use std::collections::HashMap;
use std::sync::Arc;
use oxios_kernel::approval::{
ApprovalConfig, ApprovalDecision, ApprovalGate, ApprovalMode, ExecPolicyResolver,
SecurityBlacklist, ToolCall, ToolPolicyResolver, default_blacklist_rules,
default_tool_policy_map,
};
use parking_lot::RwLock;
use serde_json::json;
fn snapshot_allowed(allowed: &[&str]) -> Arc<RwLock<Vec<String>>> {
Arc::new(RwLock::new(allowed.iter().map(|s| s.to_string()).collect()))
}
fn gate_with_exec_resolver(mode: ApprovalMode, allowed: &[&str]) -> ApprovalGate {
let policies = default_tool_policy_map();
let config = Arc::new(RwLock::new(ApprovalConfig {
mode,
allow_list: vec![],
tool_overrides: HashMap::new(),
}));
let exec_resolver = ExecPolicyResolver {
allowed_commands: snapshot_allowed(allowed),
};
let mut dynamic = HashMap::new();
dynamic.insert(
"exec".to_string(),
Box::new(exec_resolver) as Box<dyn ToolPolicyResolver>,
);
let blacklist = SecurityBlacklist::new(default_blacklist_rules());
ApprovalGate::with_dynamic_resolvers(policies, config, vec![Box::new(blacklist)], dynamic)
}
#[test]
fn structured_allowed_binary_runs_without_approval_in_manual() {
let g = gate_with_exec_resolver(ApprovalMode::Manual, &["curl"]);
let args = json!({"mode": "structured", "binary": "curl"});
let call = ToolCall {
tool: "exec",
binary: Some("curl"),
args: &args,
};
assert!(
matches!(g.evaluate(&call), ApprovalDecision::Allow),
"structured exec with allowed binary must auto-run in Manual mode"
);
}
#[test]
fn shell_mode_prompts_in_manual() {
let g = gate_with_exec_resolver(ApprovalMode::Manual, &["curl"]);
let args = json!({"mode": "shell", "command": "ls -la"});
let call = ToolCall {
tool: "exec",
binary: None,
args: &args,
};
assert!(
matches!(g.evaluate(&call), ApprovalDecision::RequireApproval { .. }),
"shell exec must prompt in Manual mode"
);
}
#[test]
fn autorun_runs_shell_without_approval() {
let g = gate_with_exec_resolver(ApprovalMode::AutoRun, &["curl"]);
let args = json!({"mode": "shell", "command": "ls -la"});
let call = ToolCall {
tool: "exec",
binary: None,
args: &args,
};
assert!(
matches!(g.evaluate(&call), ApprovalDecision::Allow),
"AutoRun mode must allow shell exec without approval"
);
}
#[test]
fn blacklist_rm_rf_prompts_even_in_autorun() {
let g = gate_with_exec_resolver(ApprovalMode::AutoRun, &["curl"]);
let args = json!({"mode": "shell", "command": "rm -rf /etc"});
let call = ToolCall {
tool: "exec",
binary: None,
args: &args,
};
assert!(
matches!(g.evaluate(&call), ApprovalDecision::RequireApproval { .. }),
"rm -rf must prompt even in AutoRun (blacklist escalation)"
);
}
#[test]
fn allow_list_grant_key_matches_exec_binary() {
let call = ToolCall {
tool: "exec",
binary: Some("curl"),
args: &json!({}),
};
assert_eq!(
call.grant_key(),
"exec:curl",
"exec grant_key must be 'exec:<binary>'"
);
let shell = ToolCall {
tool: "exec",
binary: None,
args: &json!({"mode": "shell", "command": "rm -rf /etc"}),
};
assert_eq!(
shell.grant_key(),
"exec:shell",
"shell exec grant_key must be 'exec:shell', not the command"
);
let read = ToolCall {
tool: "read",
binary: None,
args: &json!({}),
};
assert_eq!(read.grant_key(), "read");
}