use super::*;
use newt_core::caveats::{Caveats, CountBound, Scope};
use newt_core::ocap_store::Verdict;
use newt_core::{CaveatsExt as _, DenialKind, PermissionGate as _, PermissionRequest};
fn open_caveats() -> Caveats {
Caveats {
fs_read: Scope::All,
fs_write: Scope::All,
exec: Scope::All,
net: Scope::All,
max_calls: CountBound::Unlimited,
valid_for_generation: Scope::All,
}
}
fn fixture() -> (
tempfile::TempDir,
std::path::PathBuf,
std::path::PathBuf,
agent_mesh_protocol::UserKey,
) {
let dir = tempfile::TempDir::new().unwrap();
let config = dir.path().join("config.toml");
std::fs::write(
&config,
"# my config\n[tui.permissions]\npreset = \"full_access\"\n",
)
.unwrap();
let key_path = dir.path().join("identity.pem");
let root = agent_mesh_protocol::UserKey::generate();
root.save(&key_path).unwrap();
(dir, config, key_path, root)
}
fn gate<'a>(
state: &'a mut PermissionPromptState,
config: &std::path::Path,
key_path: Option<std::path::PathBuf>,
delegation: Option<&'a newt_identity::VerifiedDelegation>,
choice: PromptChoice,
) -> PromptPermissionGate<'a, impl FnMut(&PromptWindow, &SurfaceInteraction) -> PromptChoice> {
gate_with_danger(
state,
config,
key_path,
delegation,
choice,
danger::DangerTable::builtin(),
)
}
fn gate_with_danger<'a>(
state: &'a mut PermissionPromptState,
config: &std::path::Path,
key_path: Option<std::path::PathBuf>,
delegation: Option<&'a newt_identity::VerifiedDelegation>,
choice: PromptChoice,
danger: danger::DangerTable,
) -> PromptPermissionGate<'a, impl FnMut(&PromptWindow, &SurfaceInteraction) -> PromptChoice> {
let mut script = vec![choice].into_iter();
PromptPermissionGate {
pending_command_retries: Default::default(),
state,
base: open_caveats(),
key_path,
conversation_id: "conv-2535".to_string(),
log_path: None,
denials_path: None,
config_path: Some(config.to_path_buf()),
preset_clamp: None,
delegation,
danger,
color: false,
verbose: false,
authorization_prompts_enabled: true,
web_decision_timeout: Duration::from_secs(2),
cancel: None,
exit: None,
ask_surface: None,
#[cfg(feature = "rich-tui")]
open_panel: None,
ask_human: move |_w: &PromptWindow, _d: &SurfaceInteraction| {
script.next().expect("script exhausted")
},
}
}
fn req(kind: DenialKind, target: &str) -> PermissionRequest {
PermissionRequest {
tool: "run_command".to_string(),
kind,
target: target.to_string(),
reason: "not within the granted authority".to_string(),
harness_bound: false,
}
}
fn approve_path(config: &std::path::Path) -> std::path::PathBuf {
config.with_file_name("ocap").join("approve.toml")
}
#[test]
fn exec_allow_permanent_writes_a_signed_approve_entry_and_folds_the_session() {
let (_dir, config, key_path, root) = fixture();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Exec, "cargo");
match gate(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Allow(c) => assert!(c.permits_exec("cargo")),
newt_core::PermissionDecision::Deny => panic!("must be granted"),
}
assert_eq!(state.decisions.last().unwrap().scope, "permanent");
let text = std::fs::read_to_string(approve_path(&config)).unwrap();
assert!(text.contains("target = \"cargo\""), "{text}");
let file = newt_core::ocap_store::PolicyFile::parse(&text).unwrap();
let entry = file.exec.iter().find(|e| e.target == "cargo").unwrap();
assert!(entry.sig.is_some(), "must be signed");
let (set, warnings) =
newt_core::ocap_store::load_store(&config, Some(root.public().as_bytes()));
assert!(warnings.is_empty(), "{warnings:?}");
assert_eq!(
newt_core::ocap_store::evaluate_request(&set, DenialKind::Exec, "cargo"),
Some(Verdict::Approve)
);
}
#[test]
fn fs_write_of_a_broad_root_is_refused_and_store_is_untouched() {
let (dir, config, key_path, _root) = fixture();
let broad_root = dir.path().to_path_buf();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::FsWrite, &broad_root.to_string_lossy());
match gate_with_danger(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
danger::DangerTable::builtin().with_fs_root(broad_root),
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Deny => {}
newt_core::PermissionDecision::Allow(_) => panic!("a broad root must be refused"),
}
assert!(!approve_path(&config).exists(), "nothing should be written");
}
#[test]
fn exec_relative_path_is_refused_and_store_is_untouched() {
let (_dir, config, key_path, _root) = fixture();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Exec, "./x");
match gate(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Deny => {}
newt_core::PermissionDecision::Allow(_) => panic!("a relative path must be refused"),
}
assert!(!approve_path(&config).exists(), "nothing should be written");
}
#[test]
fn delegated_session_never_persists_stays_session_only() {
let (_dir, config, key_path, _root) = fixture();
let ceiling = open_caveats();
let delegation = crate::caveat_policy_tests::verified_delegation(ceiling);
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Net, "docs.rs");
match gate(
&mut state,
&config,
Some(key_path),
Some(&delegation),
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Allow(c) => assert!(c.permits_net("docs.rs")),
newt_core::PermissionDecision::Deny => panic!("must still be granted this session"),
}
assert_eq!(state.decisions.last().unwrap().scope, "session");
assert!(
state
.session_grants
.contains(&(DenialKind::Net, "docs.rs".to_string())),
"session-scoped grant must still land"
);
assert!(
!approve_path(&config).exists(),
"a delegated session must never persist"
);
}
#[test]
fn net_allow_permanent_writes_approve_toml_not_config_net() {
let (_dir, config, key_path, root) = fixture();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Net, "docs.rs");
match gate(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Allow(c) => assert!(c.permits_net("docs.rs")),
newt_core::PermissionDecision::Deny => panic!("must be granted"),
}
let config_text = std::fs::read_to_string(&config).unwrap();
assert!(
!config_text.contains("docs.rs"),
"must not land in config.toml: {config_text}"
);
let (set, warnings) =
newt_core::ocap_store::load_store(&config, Some(root.public().as_bytes()));
assert!(warnings.is_empty(), "{warnings:?}");
assert_eq!(
newt_core::ocap_store::evaluate_request(&set, DenialKind::Net, "docs.rs"),
Some(Verdict::Approve)
);
}
#[test]
fn high_danger_exec_target_is_refused_before_any_write_attempt() {
let (_dir, config, key_path, _root) = fixture();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Exec, "bash");
match gate(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Deny => {}
newt_core::PermissionDecision::Allow(_) => panic!("an interpreter must be refused"),
}
assert_eq!(
state.decisions.last().unwrap().scope,
"permanent-allow-refused-high-danger"
);
assert!(!approve_path(&config).exists(), "nothing should be written");
}
#[test]
fn unrelated_permanent_allow_does_not_launder_a_preexisting_unsigned_approve_entry() {
let (_dir, config, key_path, _root) = fixture();
let ocap_dir = config.with_file_name("ocap");
std::fs::create_dir_all(&ocap_dir).unwrap();
std::fs::write(
ocap_dir.join("approve.toml"),
"[[exec]]\ntarget = \"curl\"\n",
)
.unwrap();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Exec, "cargo");
match gate(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Allow(c) => assert!(c.permits_exec("cargo")),
newt_core::PermissionDecision::Deny => panic!("cargo must be granted"),
}
assert!(
state
.durable_grants
.contains(&(DenialKind::Exec, "cargo".to_string())),
"the entry this turn actually signed must be folded in"
);
assert!(
!state
.durable_grants
.contains(&(DenialKind::Exec, "curl".to_string())),
"an unsigned pre-existing entry must never launder into authority"
);
let (set, _warnings) =
newt_core::ocap_store::load_store(&config, Some(_root.public().as_bytes()));
assert_eq!(
newt_core::ocap_store::evaluate_request(&set, DenialKind::Exec, "curl"),
None,
"an unsigned entry must not verify even after the file is re-read"
);
}
#[test]
fn unrelated_permanent_allow_does_not_launder_a_bogus_signature_approve_entry() {
let (_dir, config, key_path, _root) = fixture();
let ocap_dir = config.with_file_name("ocap");
std::fs::create_dir_all(&ocap_dir).unwrap();
std::fs::write(
ocap_dir.join("approve.toml"),
"[[exec]]\ntarget = \"curl\"\nsig = \"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccdd\"\n",
)
.unwrap();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Exec, "cargo");
match gate(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Allow(c) => assert!(c.permits_exec("cargo")),
newt_core::PermissionDecision::Deny => panic!("cargo must be granted"),
}
assert!(
state
.durable_grants
.contains(&(DenialKind::Exec, "cargo".to_string())),
"the entry this turn actually signed must be folded in"
);
assert!(
!state
.durable_grants
.contains(&(DenialKind::Exec, "curl".to_string())),
"a bogus-signature pre-existing entry must never launder into authority"
);
}
#[test]
fn permanent_allow_with_no_config_path_gets_its_own_distinct_fallback() {
let (_dir, config, key_path, _root) = fixture();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Exec, "cargo");
let mut script = vec![PromptChoice::AllowPermanent].into_iter();
match (PromptPermissionGate {
pending_command_retries: Default::default(),
state: &mut state,
base: open_caveats(),
key_path: Some(key_path),
conversation_id: "conv-2524".to_string(),
log_path: None,
denials_path: None,
config_path: None, preset_clamp: None,
delegation: None,
danger: danger::DangerTable::builtin(),
color: false,
verbose: false,
authorization_prompts_enabled: true,
web_decision_timeout: Duration::from_secs(2),
cancel: None,
exit: None,
ask_surface: None,
#[cfg(feature = "rich-tui")]
open_panel: None,
ask_human: move |_w: &PromptWindow, _d: &SurfaceInteraction| {
script.next().expect("script exhausted")
},
})
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Allow(c) => assert!(c.permits_exec("cargo")),
newt_core::PermissionDecision::Deny => panic!("must still be granted this session"),
}
assert_eq!(state.decisions.last().unwrap().scope, "session-no-config");
let _ = config; }
#[test]
fn permanent_allow_with_no_root_key_gets_its_own_distinct_fallback() {
let (_dir, config, _key_path, _root) = fixture();
let mut state = PermissionPromptState::default();
let request = req(DenialKind::Exec, "cargo");
match gate(
&mut state,
&config,
None, None,
PromptChoice::AllowPermanent,
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Allow(c) => assert!(c.permits_exec("cargo")),
newt_core::PermissionDecision::Deny => panic!("must still be granted this session"),
}
assert_eq!(state.decisions.last().unwrap().scope, "session-no-key");
assert!(!approve_path(&config).exists(), "nothing should be written");
}
#[test]
fn no_key_notice_names_the_actual_path_not_the_literal_word() {
let configured = std::path::Path::new("/opt/newt-fixture/identity.pem");
let with_path = no_key_notice(Some(configured));
assert!(
with_path.contains("/opt/newt-fixture/identity.pem"),
"must name the real path: {with_path}"
);
assert!(
!with_path.contains("`key_path`"),
"must not print the literal placeholder word: {with_path}"
);
let without_path = no_key_notice(None);
assert!(
without_path.contains("no key_path configured"),
"an unconfigured key_path must say so plainly: {without_path}"
);
assert!(
!without_path.contains("`key_path`"),
"must not print the literal placeholder word either: {without_path}"
);
}
#[test]
fn danger_target_is_normalized_before_classification() {
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path();
let ws = root.join("ws");
let climbing = ws.join("..");
assert_eq!(
std::path::Path::new(&normalized_danger_target(
DenialKind::FsWrite,
&climbing.to_string_lossy()
)),
root,
"a climbing fs target must be classified against its normalized form"
);
let contained = ws.join("x").join("..").join("y");
assert_eq!(
std::path::Path::new(&normalized_danger_target(
DenialKind::FsRead,
&contained.to_string_lossy()
)),
ws.join("y").as_path(),
"an internally-contained relative segment normalizes too"
);
assert_eq!(
normalized_danger_target(DenialKind::FsWrite, "../../"),
"../../"
);
assert_eq!(normalized_danger_target(DenialKind::Exec, "bash"), "bash");
assert_eq!(
normalized_danger_target(DenialKind::Net, "evil.example"),
"evil.example"
);
}
#[test]
fn allow_permanent_refuses_a_climbing_path_that_normalizes_to_a_broad_root() {
let (dir, config, key_path, _root) = fixture();
let broad_root = dir.path().to_path_buf();
let climbing = broad_root.join("ws").join("..");
let mut state = PermissionPromptState::default();
let request = req(DenialKind::FsWrite, &climbing.to_string_lossy());
match gate_with_danger(
&mut state,
&config,
Some(key_path),
None,
PromptChoice::AllowPermanent,
danger::DangerTable::builtin().with_fs_root(broad_root),
)
.ask(std::slice::from_ref(&request))
{
newt_core::PermissionDecision::Deny => {}
newt_core::PermissionDecision::Allow(_) => {
panic!("a climbing path to a broad root must be refused")
}
}
assert_eq!(
state.decisions.last().unwrap().scope,
"permanent-allow-refused-high-danger"
);
assert!(!approve_path(&config).exists(), "nothing should be written");
}
#[test]
fn persist_approve_reports_when_preexisting_comments_were_dropped() {
let dir = tempfile::TempDir::new().unwrap();
let config = dir.path().join("config.toml");
let ocap_dir = config.with_file_name("ocap");
std::fs::create_dir_all(&ocap_dir).unwrap();
std::fs::write(
ocap_dir.join("approve.toml"),
"# hand-written note\n[[exec]]\ntarget = \"curl\"\nsig = \"aa\"\n",
)
.unwrap();
let k = agent_mesh_protocol::UserKey::generate();
let had_comments = newt_core::ocap_store::persist_approve(
&config,
newt_core::ocap_store::ApproveEntry::Exec {
target: "cargo".to_string(),
},
|_, _| false,
|payload| k.sign(payload).to_bytes(),
)
.unwrap();
assert!(had_comments, "the pre-existing file had a comment");
let had_comments_again = newt_core::ocap_store::persist_approve(
&config,
newt_core::ocap_store::ApproveEntry::Exec {
target: "just".to_string(),
},
|_, _| false,
|payload| k.sign(payload).to_bytes(),
)
.unwrap();
assert!(
!had_comments_again,
"a re-serialised file (no hand-written comment left) must report false"
);
}