command auth has been relaxed by default
This commit is contained in:
parent
f549e5ae55
commit
46e99b749b
@ -3820,7 +3820,10 @@ mod tests {
|
||||
tool_call_id: None,
|
||||
},
|
||||
];
|
||||
let approval_cfg = crate::config::AutonomyConfig::default();
|
||||
let approval_cfg = crate::config::AutonomyConfig {
|
||||
level: crate::security::AutonomyLevel::Supervised,
|
||||
..crate::config::AutonomyConfig::default()
|
||||
};
|
||||
let approval_mgr = ApprovalManager::from_config(&approval_cfg);
|
||||
|
||||
assert!(!should_execute_tools_in_parallel(
|
||||
|
||||
@ -1997,9 +1997,14 @@ pub struct BuiltinHooksConfig {
|
||||
///
|
||||
/// Controls what the agent is allowed to do: shell commands, filesystem access,
|
||||
/// risk approval gates, and per-policy budgets.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AutonomyConfig {
|
||||
/// Autonomy level: `read_only`, `supervised` (default), or `full`.
|
||||
/// Master toggle for shell security policy enforcement.
|
||||
/// When false (default), command restrictions, path blocks, and rate limits are disabled.
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
/// Autonomy level: `read_only`, `supervised`, or `full` (default).
|
||||
pub level: AutonomyLevel,
|
||||
/// Restrict absolute filesystem paths to workspace-relative references. Default: `true`.
|
||||
/// Resolved paths outside the workspace still require `allowed_roots`.
|
||||
@ -2070,7 +2075,8 @@ fn is_valid_env_var_name(name: &str) -> bool {
|
||||
impl Default for AutonomyConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
level: AutonomyLevel::Supervised,
|
||||
enabled: false,
|
||||
level: AutonomyLevel::Full,
|
||||
workspace_only: true,
|
||||
allowed_commands: vec![
|
||||
"git".into(),
|
||||
@ -4546,6 +4552,19 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
// Security policy master toggle: ZEROCLAW_SECURITY_POLICY
|
||||
if let Ok(flag) = std::env::var("ZEROCLAW_SECURITY_POLICY") {
|
||||
if !flag.trim().is_empty() {
|
||||
match flag.trim().to_ascii_lowercase().as_str() {
|
||||
"1" | "true" | "yes" | "on" => self.autonomy.enabled = true,
|
||||
"0" | "false" | "no" | "off" => self.autonomy.enabled = false,
|
||||
_ => tracing::warn!(
|
||||
"Ignoring invalid ZEROCLAW_SECURITY_POLICY (valid: 1|0|true|false|yes|no|on|off)"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gateway port: ZEROCLAW_GATEWAY_PORT or PORT
|
||||
if let Ok(port_str) =
|
||||
std::env::var("ZEROCLAW_GATEWAY_PORT").or_else(|_| std::env::var("PORT"))
|
||||
@ -5000,7 +5019,8 @@ mod tests {
|
||||
#[test]
|
||||
async fn autonomy_config_default() {
|
||||
let a = AutonomyConfig::default();
|
||||
assert_eq!(a.level, AutonomyLevel::Supervised);
|
||||
assert!(!a.enabled);
|
||||
assert_eq!(a.level, AutonomyLevel::Full);
|
||||
assert!(a.workspace_only);
|
||||
assert!(a.allowed_commands.contains(&"git".to_string()));
|
||||
assert!(a.allowed_commands.contains(&"cargo".to_string()));
|
||||
@ -5131,6 +5151,7 @@ default_temperature = 0.7
|
||||
..ObservabilityConfig::default()
|
||||
},
|
||||
autonomy: AutonomyConfig {
|
||||
enabled: false,
|
||||
level: AutonomyLevel::Full,
|
||||
workspace_only: false,
|
||||
allowed_commands: vec!["docker".into()],
|
||||
@ -5256,7 +5277,7 @@ default_temperature = 0.7
|
||||
assert!(parsed.default_provider.is_none());
|
||||
assert_eq!(parsed.observability.backend, "none");
|
||||
assert_eq!(parsed.observability.runtime_trace_mode, "none");
|
||||
assert_eq!(parsed.autonomy.level, AutonomyLevel::Supervised);
|
||||
assert_eq!(parsed.autonomy.level, AutonomyLevel::Full);
|
||||
assert_eq!(parsed.runtime.kind, "native");
|
||||
assert!(!parsed.heartbeat.enabled);
|
||||
assert!(parsed.channels_config.cli);
|
||||
|
||||
@ -569,6 +569,7 @@ mod tests {
|
||||
async fn run_job_command_blocks_forbidden_path_argument() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut config = test_config(&tmp).await;
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.allowed_commands = vec!["cat".into()];
|
||||
let job = test_job("cat /etc/passwd");
|
||||
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||
@ -584,6 +585,7 @@ mod tests {
|
||||
async fn run_job_command_blocks_forbidden_option_assignment_path_argument() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut config = test_config(&tmp).await;
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.allowed_commands = vec!["grep".into()];
|
||||
let job = test_job("grep --file=/etc/passwd root ./src");
|
||||
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||
@ -599,6 +601,7 @@ mod tests {
|
||||
async fn run_job_command_blocks_forbidden_short_option_attached_path_argument() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut config = test_config(&tmp).await;
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.allowed_commands = vec!["grep".into()];
|
||||
let job = test_job("grep -f/etc/passwd root ./src");
|
||||
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||
@ -614,6 +617,7 @@ mod tests {
|
||||
async fn run_job_command_blocks_tilde_user_path_argument() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut config = test_config(&tmp).await;
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.allowed_commands = vec!["cat".into()];
|
||||
let job = test_job("cat ~root/.ssh/id_rsa");
|
||||
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||
@ -657,6 +661,7 @@ mod tests {
|
||||
async fn run_job_command_blocks_rate_limited() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut config = test_config(&tmp).await;
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.max_actions_per_hour = 0;
|
||||
let job = test_job("echo should-not-run");
|
||||
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
|
||||
@ -738,6 +743,7 @@ mod tests {
|
||||
async fn run_agent_job_blocks_rate_limited() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut config = test_config(&tmp).await;
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.max_actions_per_hour = 0;
|
||||
let mut job = test_job("");
|
||||
job.job_type = JobType::Agent;
|
||||
|
||||
@ -78,7 +78,7 @@ mod tests {
|
||||
#[test]
|
||||
fn reexported_policy_and_pairing_types_are_usable() {
|
||||
let policy = SecurityPolicy::default();
|
||||
assert_eq!(policy.autonomy, AutonomyLevel::Supervised);
|
||||
assert_eq!(policy.autonomy, AutonomyLevel::Full);
|
||||
|
||||
let guard = PairingGuard::new(false, &[]);
|
||||
assert!(!guard.require_pairing());
|
||||
|
||||
@ -78,8 +78,11 @@ impl Clone for ActionTracker {
|
||||
}
|
||||
|
||||
/// Security policy enforced on all tool executions
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SecurityPolicy {
|
||||
/// When false, command restrictions, path blocks, and rate limits are bypassed.
|
||||
pub enabled: bool,
|
||||
pub autonomy: AutonomyLevel,
|
||||
pub workspace_dir: PathBuf,
|
||||
pub workspace_only: bool,
|
||||
@ -97,7 +100,8 @@ pub struct SecurityPolicy {
|
||||
impl Default for SecurityPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
enabled: false,
|
||||
autonomy: AutonomyLevel::Full,
|
||||
workspace_dir: PathBuf::from("."),
|
||||
workspace_only: true,
|
||||
allowed_commands: vec![
|
||||
@ -673,6 +677,9 @@ impl SecurityPolicy {
|
||||
command: &str,
|
||||
approved: bool,
|
||||
) -> Result<CommandRiskLevel, String> {
|
||||
if !self.enabled {
|
||||
return Ok(CommandRiskLevel::Low);
|
||||
}
|
||||
if !self.is_command_allowed(command) {
|
||||
return Err(format!("Command not allowed by security policy: {command}"));
|
||||
}
|
||||
@ -824,6 +831,9 @@ impl SecurityPolicy {
|
||||
/// This is best-effort token parsing for shell commands and is intended
|
||||
/// as a safety gate before command execution.
|
||||
pub fn forbidden_path_argument(&self, command: &str) -> Option<String> {
|
||||
if !self.enabled {
|
||||
return None;
|
||||
}
|
||||
let forbidden_candidate = |raw: &str| {
|
||||
let candidate = strip_wrapping_quotes(raw).trim();
|
||||
if candidate.is_empty() || candidate.contains("://") {
|
||||
@ -1033,12 +1043,18 @@ impl SecurityPolicy {
|
||||
/// Record an action and check if the rate limit has been exceeded.
|
||||
/// Returns `true` if the action is allowed, `false` if rate-limited.
|
||||
pub fn record_action(&self) -> bool {
|
||||
if !self.enabled {
|
||||
return true;
|
||||
}
|
||||
let count = self.tracker.record();
|
||||
count <= self.max_actions_per_hour as usize
|
||||
}
|
||||
|
||||
/// Check if the rate limit would be exceeded without recording.
|
||||
pub fn is_rate_limited(&self) -> bool {
|
||||
if !self.enabled {
|
||||
return false;
|
||||
}
|
||||
self.tracker.count() >= self.max_actions_per_hour as usize
|
||||
}
|
||||
|
||||
@ -1048,6 +1064,7 @@ impl SecurityPolicy {
|
||||
workspace_dir: &Path,
|
||||
) -> Self {
|
||||
Self {
|
||||
enabled: autonomy_config.enabled,
|
||||
autonomy: autonomy_config.level,
|
||||
workspace_dir: workspace_dir.to_path_buf(),
|
||||
workspace_only: autonomy_config.workspace_only,
|
||||
@ -1079,12 +1096,18 @@ impl SecurityPolicy {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Returns an **enabled** policy with supervised autonomy for enforcement tests.
|
||||
fn default_policy() -> SecurityPolicy {
|
||||
SecurityPolicy::default()
|
||||
SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
..SecurityPolicy::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn readonly_policy() -> SecurityPolicy {
|
||||
SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
}
|
||||
@ -1092,6 +1115,7 @@ mod tests {
|
||||
|
||||
fn full_policy() -> SecurityPolicy {
|
||||
SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Full,
|
||||
..SecurityPolicy::default()
|
||||
}
|
||||
@ -1218,6 +1242,7 @@ mod tests {
|
||||
#[test]
|
||||
fn allowlist_supports_wildcard_entry() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
allowed_commands: vec!["*".into()],
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
@ -1309,6 +1334,7 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_command_requires_approval_for_medium_risk() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
require_approval_for_medium_risk: true,
|
||||
allowed_commands: vec!["touch".into()],
|
||||
@ -1326,6 +1352,7 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_command_blocks_high_risk_by_default() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
allowed_commands: vec!["rm".into()],
|
||||
..SecurityPolicy::default()
|
||||
@ -1339,6 +1366,7 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_command_full_mode_skips_medium_risk_approval_gate() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Full,
|
||||
require_approval_for_medium_risk: true,
|
||||
allowed_commands: vec!["touch".into()],
|
||||
@ -1424,6 +1452,7 @@ mod tests {
|
||||
#[test]
|
||||
fn from_config_maps_all_fields() {
|
||||
let autonomy_config = crate::config::AutonomyConfig {
|
||||
enabled: true,
|
||||
level: AutonomyLevel::Full,
|
||||
workspace_only: false,
|
||||
allowed_commands: vec!["docker".into()],
|
||||
@ -1438,6 +1467,7 @@ mod tests {
|
||||
let workspace = PathBuf::from("/tmp/test-workspace");
|
||||
let policy = SecurityPolicy::from_config(&autonomy_config, &workspace);
|
||||
|
||||
assert!(policy.enabled);
|
||||
assert_eq!(policy.autonomy, AutonomyLevel::Full);
|
||||
assert!(!policy.workspace_only);
|
||||
assert_eq!(policy.allowed_commands, vec!["docker"]);
|
||||
@ -1482,7 +1512,8 @@ mod tests {
|
||||
#[test]
|
||||
fn default_policy_has_sane_values() {
|
||||
let p = SecurityPolicy::default();
|
||||
assert_eq!(p.autonomy, AutonomyLevel::Supervised);
|
||||
assert!(!p.enabled);
|
||||
assert_eq!(p.autonomy, AutonomyLevel::Full);
|
||||
assert!(p.workspace_only);
|
||||
assert!(!p.allowed_commands.is_empty());
|
||||
assert!(!p.forbidden_paths.is_empty());
|
||||
@ -1513,6 +1544,7 @@ mod tests {
|
||||
#[test]
|
||||
fn record_action_allows_within_limit() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 5,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
@ -1524,6 +1556,7 @@ mod tests {
|
||||
#[test]
|
||||
fn record_action_blocks_over_limit() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 3,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
@ -1536,6 +1569,7 @@ mod tests {
|
||||
#[test]
|
||||
fn is_rate_limited_reflects_count() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 2,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
@ -1901,6 +1935,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rate_limit_exactly_at_boundary() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 1,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
@ -1912,6 +1947,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rate_limit_zero_blocks_everything() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
@ -1921,6 +1957,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rate_limit_high_allows_many() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 10000,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
@ -2335,4 +2372,36 @@ mod tests {
|
||||
"URL-encoded parent dir traversal must be blocked"
|
||||
);
|
||||
}
|
||||
|
||||
// ── Disabled policy (enabled=false) ──────────────────────
|
||||
|
||||
#[test]
|
||||
fn disabled_policy_allows_any_command() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: false,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
let result = p.validate_command_execution("rm -rf /", false);
|
||||
assert_eq!(result.unwrap(), CommandRiskLevel::Low);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_policy_allows_forbidden_paths() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: false,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
assert!(p.forbidden_path_argument("cat /etc/passwd").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_policy_skips_rate_limit() {
|
||||
let p = SecurityPolicy {
|
||||
enabled: false,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
};
|
||||
assert!(!p.is_rate_limited());
|
||||
assert!(p.record_action());
|
||||
}
|
||||
}
|
||||
|
||||
@ -503,6 +503,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_blocks_readonly_mode() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
@ -518,6 +519,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_blocks_when_rate_limited() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
|
||||
@ -1419,6 +1419,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_blocked_in_readonly_mode() {
|
||||
let readonly = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
@ -1441,6 +1442,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_blocked_when_rate_limited() {
|
||||
let limited = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
|
||||
@ -660,6 +660,7 @@ mod tests {
|
||||
|
||||
fn test_security(workspace: PathBuf) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: workspace,
|
||||
..SecurityPolicy::default()
|
||||
@ -672,6 +673,7 @@ mod tests {
|
||||
max_actions_per_hour: u32,
|
||||
) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy,
|
||||
workspace_dir: workspace,
|
||||
max_actions_per_hour,
|
||||
|
||||
@ -322,6 +322,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.allowed_commands = vec!["echo".into()];
|
||||
config.autonomy.level = AutonomyLevel::Supervised;
|
||||
tokio::fs::create_dir_all(&config.workspace_dir)
|
||||
@ -378,6 +379,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Full;
|
||||
config.autonomy.max_actions_per_hour = 0;
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
@ -409,6 +411,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.allowed_commands = vec!["touch".into()];
|
||||
config.autonomy.level = AutonomyLevel::Supervised;
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
|
||||
@ -185,6 +185,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Full;
|
||||
config.autonomy.max_actions_per_hour = 0;
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
|
||||
@ -230,6 +230,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Supervised;
|
||||
config.autonomy.allowed_commands = vec!["touch".into()];
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
@ -259,6 +260,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Full;
|
||||
config.autonomy.max_actions_per_hour = 0;
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
|
||||
@ -201,6 +201,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.allowed_commands = vec!["echo".into()];
|
||||
tokio::fs::create_dir_all(&config.workspace_dir)
|
||||
.await
|
||||
@ -253,6 +254,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Supervised;
|
||||
config.autonomy.allowed_commands = vec!["echo".into(), "touch".into()];
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
@ -292,6 +294,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Full;
|
||||
config.autonomy.max_actions_per_hour = 0;
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
|
||||
@ -850,6 +850,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn delegation_blocked_in_readonly_mode() {
|
||||
let readonly = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
@ -869,6 +870,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn delegation_blocked_when_rate_limited() {
|
||||
let limited = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
|
||||
@ -229,6 +229,7 @@ mod tests {
|
||||
|
||||
fn test_security(workspace: std::path::PathBuf) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: workspace,
|
||||
..SecurityPolicy::default()
|
||||
@ -241,6 +242,7 @@ mod tests {
|
||||
max_actions_per_hour: u32,
|
||||
) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy,
|
||||
workspace_dir: workspace,
|
||||
max_actions_per_hour,
|
||||
|
||||
@ -240,6 +240,7 @@ mod tests {
|
||||
|
||||
fn test_security(workspace: std::path::PathBuf) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: workspace,
|
||||
..SecurityPolicy::default()
|
||||
@ -252,6 +253,7 @@ mod tests {
|
||||
max_actions_per_hour: u32,
|
||||
) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy,
|
||||
workspace_dir: workspace,
|
||||
max_actions_per_hour,
|
||||
|
||||
@ -168,6 +168,7 @@ mod tests {
|
||||
|
||||
fn test_security(workspace: std::path::PathBuf) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: workspace,
|
||||
..SecurityPolicy::default()
|
||||
@ -180,6 +181,7 @@ mod tests {
|
||||
max_actions_per_hour: u32,
|
||||
) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy,
|
||||
workspace_dir: workspace,
|
||||
max_actions_per_hour,
|
||||
|
||||
@ -179,6 +179,7 @@ mod tests {
|
||||
|
||||
fn test_security(workspace: PathBuf) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: workspace,
|
||||
..SecurityPolicy::default()
|
||||
@ -191,6 +192,7 @@ mod tests {
|
||||
max_actions_per_hour: u32,
|
||||
) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy,
|
||||
workspace_dir: workspace,
|
||||
max_actions_per_hour,
|
||||
|
||||
@ -683,6 +683,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_blocks_readonly_mode() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
@ -698,6 +699,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_blocks_when_rate_limited() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
|
||||
@ -142,6 +142,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
let readonly = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
@ -163,6 +164,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
let limited = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
|
||||
@ -184,6 +184,7 @@ mod tests {
|
||||
async fn store_blocked_in_readonly_mode() {
|
||||
let (_tmp, mem) = test_mem();
|
||||
let readonly = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
@ -205,6 +206,7 @@ mod tests {
|
||||
async fn store_blocked_when_rate_limited() {
|
||||
let (_tmp, mem) = test_mem();
|
||||
let limited = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
|
||||
@ -236,6 +236,7 @@ mod tests {
|
||||
|
||||
fn test_security(workspace: std::path::PathBuf) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: workspace,
|
||||
..SecurityPolicy::default()
|
||||
@ -247,6 +248,7 @@ mod tests {
|
||||
max_actions: u32,
|
||||
) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: workspace,
|
||||
max_actions_per_hour: max_actions,
|
||||
|
||||
@ -223,6 +223,7 @@ mod tests {
|
||||
|
||||
fn test_security(level: AutonomyLevel, max_actions_per_hour: u32) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: level,
|
||||
max_actions_per_hour,
|
||||
workspace_dir: std::env::temp_dir(),
|
||||
|
||||
@ -558,6 +558,7 @@ mod tests {
|
||||
workspace_dir: tmp.path().join("workspace"),
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
autonomy: crate::config::AutonomyConfig {
|
||||
enabled: true,
|
||||
level: AutonomyLevel::Full,
|
||||
max_actions_per_hour: 0,
|
||||
..Default::default()
|
||||
@ -600,6 +601,7 @@ mod tests {
|
||||
workspace_dir: tmp.path().join("workspace"),
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
autonomy: crate::config::AutonomyConfig {
|
||||
enabled: true,
|
||||
level: AutonomyLevel::Full,
|
||||
max_actions_per_hour: 1,
|
||||
..Default::default()
|
||||
@ -696,6 +698,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Supervised;
|
||||
config.autonomy.allowed_commands = vec!["echo".into()];
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
@ -730,6 +733,7 @@ mod tests {
|
||||
config_path: tmp.path().join("config.toml"),
|
||||
..Config::default()
|
||||
};
|
||||
config.autonomy.enabled = true;
|
||||
config.autonomy.level = AutonomyLevel::Supervised;
|
||||
config.autonomy.allowed_commands = vec!["touch".into()];
|
||||
std::fs::create_dir_all(&config.workspace_dir).unwrap();
|
||||
|
||||
@ -214,6 +214,7 @@ mod tests {
|
||||
|
||||
fn test_security(autonomy: AutonomyLevel) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy,
|
||||
workspace_dir: std::env::temp_dir(),
|
||||
..SecurityPolicy::default()
|
||||
@ -389,6 +390,7 @@ mod tests {
|
||||
|
||||
fn test_security_with_env_cmd() -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: std::env::temp_dir(),
|
||||
allowed_commands: vec!["env".into(), "echo".into()],
|
||||
@ -398,6 +400,7 @@ mod tests {
|
||||
|
||||
fn test_security_with_env_passthrough(vars: &[&str]) -> Arc<SecurityPolicy> {
|
||||
Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
workspace_dir: std::env::temp_dir(),
|
||||
allowed_commands: vec!["env".into()],
|
||||
@ -524,6 +527,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn shell_requires_approval_for_medium_risk_command() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
allowed_commands: vec!["touch".into()],
|
||||
workspace_dir: std::env::temp_dir(),
|
||||
@ -602,6 +606,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn shell_blocks_rate_limited() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Supervised,
|
||||
max_actions_per_hour: 0,
|
||||
workspace_dir: std::env::temp_dir(),
|
||||
@ -644,6 +649,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn shell_record_action_budget_exhaustion() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::Full,
|
||||
max_actions_per_hour: 1,
|
||||
workspace_dir: std::env::temp_dir(),
|
||||
|
||||
@ -711,6 +711,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn blocks_readonly_mode() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
autonomy: AutonomyLevel::ReadOnly,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
@ -726,6 +727,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn blocks_rate_limited() {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
enabled: true,
|
||||
max_actions_per_hour: 0,
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
|
||||
@ -175,12 +175,16 @@ fn security_config_toml_roundtrip() {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn autonomy_config_default_is_supervised() {
|
||||
fn autonomy_config_default_is_full() {
|
||||
let autonomy = AutonomyConfig::default();
|
||||
assert!(
|
||||
!autonomy.enabled,
|
||||
"default security policy should be disabled"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{:?}", autonomy.level),
|
||||
"Supervised",
|
||||
"default autonomy should be Supervised"
|
||||
"Full",
|
||||
"default autonomy should be Full"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user