fix(security): enforce cron agent autonomy and rate gates (#626)
This commit is contained in:
parent
861137b2b3
commit
c649ced585
1 changed files with 62 additions and 8 deletions
|
|
@ -61,7 +61,7 @@ async fn execute_job_with_retry(
|
||||||
for attempt in 0..=retries {
|
for attempt in 0..=retries {
|
||||||
let (success, output) = match job.job_type {
|
let (success, output) = match job.job_type {
|
||||||
JobType::Shell => run_job_command(config, security, job).await,
|
JobType::Shell => run_job_command(config, security, job).await,
|
||||||
JobType::Agent => run_agent_job(config, job).await,
|
JobType::Agent => run_agent_job(config, security, job).await,
|
||||||
};
|
};
|
||||||
last_output = output;
|
last_output = output;
|
||||||
|
|
||||||
|
|
@ -116,7 +116,31 @@ async fn execute_and_persist_job(
|
||||||
(job.id.clone(), success)
|
(job.id.clone(), success)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_agent_job(config: &Config, job: &CronJob) -> (bool, String) {
|
async fn run_agent_job(
|
||||||
|
config: &Config,
|
||||||
|
security: &SecurityPolicy,
|
||||||
|
job: &CronJob,
|
||||||
|
) -> (bool, String) {
|
||||||
|
if !security.can_act() {
|
||||||
|
return (
|
||||||
|
false,
|
||||||
|
"blocked by security policy: autonomy is read-only".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if security.is_rate_limited() {
|
||||||
|
return (
|
||||||
|
false,
|
||||||
|
"blocked by security policy: rate limit exceeded".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !security.record_action() {
|
||||||
|
return (
|
||||||
|
false,
|
||||||
|
"blocked by security policy: action budget exhausted".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
let name = job.name.clone().unwrap_or_else(|| "cron-job".to_string());
|
let name = job.name.clone().unwrap_or_else(|| "cron-job".to_string());
|
||||||
let prompt = job.prompt.clone().unwrap_or_default();
|
let prompt = job.prompt.clone().unwrap_or_default();
|
||||||
let prefixed_prompt = format!("[cron:{} {name}] {prompt}", job.id);
|
let prefixed_prompt = format!("[cron:{} {name}] {prompt}", job.id);
|
||||||
|
|
@ -653,13 +677,43 @@ mod tests {
|
||||||
let mut job = test_job("");
|
let mut job = test_job("");
|
||||||
job.job_type = JobType::Agent;
|
job.job_type = JobType::Agent;
|
||||||
job.prompt = Some("Say hello".into());
|
job.prompt = Some("Say hello".into());
|
||||||
|
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||||
|
|
||||||
let (success, output) = run_agent_job(&config, &job).await;
|
let (success, output) = run_agent_job(&config, &security, &job).await;
|
||||||
assert!(!success, "Agent job without provider key should fail");
|
assert!(!success);
|
||||||
assert!(
|
assert!(output.contains("agent job failed:"));
|
||||||
!output.is_empty(),
|
}
|
||||||
"Expected non-empty error output from failed agent job"
|
|
||||||
);
|
#[tokio::test]
|
||||||
|
async fn run_agent_job_blocks_readonly_mode() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let mut config = test_config(&tmp);
|
||||||
|
config.autonomy.level = crate::security::AutonomyLevel::ReadOnly;
|
||||||
|
let mut job = test_job("");
|
||||||
|
job.job_type = JobType::Agent;
|
||||||
|
job.prompt = Some("Say hello".into());
|
||||||
|
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||||
|
|
||||||
|
let (success, output) = run_agent_job(&config, &security, &job).await;
|
||||||
|
assert!(!success);
|
||||||
|
assert!(output.contains("blocked by security policy"));
|
||||||
|
assert!(output.contains("read-only"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn run_agent_job_blocks_rate_limited() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let mut config = test_config(&tmp);
|
||||||
|
config.autonomy.max_actions_per_hour = 0;
|
||||||
|
let mut job = test_job("");
|
||||||
|
job.job_type = JobType::Agent;
|
||||||
|
job.prompt = Some("Say hello".into());
|
||||||
|
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||||
|
|
||||||
|
let (success, output) = run_agent_job(&config, &security, &job).await;
|
||||||
|
assert!(!success);
|
||||||
|
assert!(output.contains("blocked by security policy"));
|
||||||
|
assert!(output.contains("rate limit exceeded"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue