use aethershell::env::Env;
use aethershell::value::Value;
use std::sync::Mutex;
static ENV: Mutex<()> = Mutex::new(());
fn call(name: &str, args: Vec<&str>) -> Result<Value, String> {
let mut env = Env::new();
let args = args
.into_iter()
.map(|a| Value::Str(a.to_string()))
.collect();
aethershell::builtins::call(name, args, &mut env).map_err(|e| e.to_string())
}
fn agent_mode() -> std::path::PathBuf {
let ws = std::env::temp_dir().join("ae_privesc");
std::fs::create_dir_all(&ws).unwrap();
std::env::set_var("AETHER_MODE", "agent");
std::env::set_var("AETHER_WORKSPACE", &ws);
std::env::remove_var("AETHER_POLICY");
aethershell::safety::set_principal(None);
ws
}
fn human_mode() {
std::env::set_var("AETHER_MODE", "human");
aethershell::safety::set_principal(None);
}
#[test]
fn an_agent_cannot_grant_itself_permissions() {
let _l = ENV.lock().unwrap_or_else(|e| e.into_inner());
agent_mode();
let err = call("rbac_grant", vec!["escalator", "effect:*"])
.expect_err("granting a permission must not be allowed in agent mode");
assert!(
err.contains("E_POLICY_DENY"),
"expected a policy denial, got: {err}"
);
}
#[test]
fn an_agent_cannot_change_who_it_acts_as() {
let _l = ENV.lock().unwrap_or_else(|e| e.into_inner());
agent_mode();
let err = call("rbac_principal", vec!["someone_else"])
.expect_err("switching principal must not be allowed in agent mode");
assert!(
err.contains("E_POLICY_DENY"),
"expected a policy denial, got: {err}"
);
}
#[test]
fn the_escalation_path_end_to_end_is_closed() {
let _l = ENV.lock().unwrap_or_else(|e| e.into_inner());
let ws = agent_mode();
let victim = ws.join("victim.txt");
std::fs::write(&victim, "please do not delete me").unwrap();
let path = victim.to_string_lossy().to_string();
let before = call("rm", vec![&path]).expect_err("rm should be gated in agent mode");
assert!(
before.contains("E_NEEDS_APPROVAL"),
"baseline is not an approval gate, so the rest proves nothing: {before}"
);
let _ = call("rbac_grant", vec!["escalator", "effect:*"]);
let _ = call("rbac_principal", vec!["escalator"]);
let after = call("rm", vec![&path]).expect_err("rm must still be gated after the attempt");
assert!(
after.contains("E_NEEDS_APPROVAL"),
"self-granted privileges bypassed approval: {after}"
);
assert!(victim.exists(), "the file was deleted despite the gate");
let _ = std::fs::remove_file(&victim);
}
#[test]
fn a_human_session_can_still_administer_rbac() {
let _l = ENV.lock().unwrap_or_else(|e| e.into_inner());
human_mode();
call("rbac_grant", vec!["operator", "effect:destructive"])
.expect("a human session administers RBAC");
call("rbac_principal", vec!["operator"]).expect("a human session may set the principal");
assert_eq!(
aethershell::safety::current_principal().as_deref(),
Some("operator")
);
aethershell::safety::set_principal(None);
}
#[test]
fn reading_authorization_state_stays_ungated() {
let _l = ENV.lock().unwrap_or_else(|e| e.into_inner());
agent_mode();
call("rbac_can", vec!["effect:destructive"]).expect("asking is not escalating");
let mut env = Env::new();
aethershell::builtins::call("rbac_principal", vec![], &mut env)
.expect("reading the current principal is not escalating");
}