use aethershell::safety::{self, Effect};
use aethershell::value::Value;
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn lock() -> std::sync::MutexGuard<'static, ()> {
ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
fn agent_workspace(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("ae_exec_gate_{}_{}", tag, std::process::id()));
let _ = std::fs::create_dir_all(&dir);
std::env::set_var("AETHER_WORKSPACE", &dir);
std::env::set_var("AETHER_AUDIT_LOG", dir.join("audit.log"));
std::env::set_var("AETHER_MODE", "agent");
std::env::remove_var("AETHER_APPROVE");
std::env::remove_var("AETHER_APPROVE_ALL");
safety::governor_reset();
dir
}
fn leave_agent_mode() {
for k in [
"AETHER_MODE",
"AETHER_WORKSPACE",
"AETHER_AUDIT_LOG",
"AETHER_APPROVE",
"AETHER_APPROVE_ALL",
] {
std::env::remove_var(k);
}
}
#[test]
fn timeout_cannot_run_a_command_unapproved_in_agent_mode() {
let _l = lock();
let dir = agent_workspace("timeout");
let marker = dir.join("PWNED.txt");
let _ = std::fs::remove_file(&marker);
let mut env = aethershell::env::Env::new();
let res = aethershell::builtins::call(
"timeout_cmd",
vec![
Value::Int(5),
Value::Str(format!("touch {}", marker.display())),
],
&mut env,
);
assert!(
res.is_err(),
"timeout ran a command in agent mode without approval: {res:?}"
);
assert!(
!marker.exists(),
"the command actually executed — the gate is decorative"
);
leave_agent_mode();
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn proc_spawn_cannot_launch_a_program_unapproved_in_agent_mode() {
let _l = lock();
let dir = agent_workspace("spawn");
let mut env = aethershell::env::Env::new();
let program = if cfg!(windows) { "cmd" } else { "true" };
let res = aethershell::builtins::call(
"proc_spawn",
vec![Value::Str(program.to_string())],
&mut env,
);
assert!(
res.is_err(),
"proc.spawn launched a program in agent mode without approval: {res:?}"
);
leave_agent_mode();
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn human_mode_is_unaffected() {
let _l = lock();
leave_agent_mode();
let dir = std::env::temp_dir().join(format!("ae_exec_gate_human_{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
let marker = dir.join("ok.txt");
let _ = std::fs::remove_file(&marker);
let mut env = aethershell::env::Env::new();
let res = aethershell::builtins::call(
"timeout_cmd",
vec![
Value::Int(5),
Value::Str(format!("touch {}", marker.display())),
],
&mut env,
);
if let Err(e) = &res {
let rendered = e.to_string();
for refusal in ["E_NEEDS_APPROVAL", "E_POLICY_DENY", "E_OUTSIDE_WORKSPACE"] {
assert!(
!rendered.contains(refusal),
"human mode must not be gated, but got {refusal}: {rendered}"
);
}
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn every_guarded_exec_builtin_is_classified_as_exec() {
for name in [
"sh",
"timeout_cmd",
"xargs_exec",
"proc_spawn",
"nohup_run",
"strace_cmd",
"ltrace_cmd",
"perf_stat",
"perf_record",
"lxc_exec",
] {
assert_eq!(
safety::effect_of(name),
Effect::Exec,
"{name} runs a caller-supplied command but is not classified as exec"
);
}
}