#![allow(clippy::pedantic, clippy::nursery, missing_docs)]
use serde_json::json;
use super::*;
fn spec(name: &str) -> ToolSpec {
ToolSpec::new(name, "d", json!({}))
}
fn profile(origin: ToolOrigin) -> ToolProfile {
ToolProfile {
origin,
read_only: false,
destructive: false,
open_world: false,
}
}
const fn set(caps: &[Capability]) -> CapabilitySet {
let mut s = CapabilitySet::EMPTY;
let mut i = 0;
while i < caps.len() {
s = s.with(caps[i]);
i += 1;
}
s
}
#[test]
fn set_algebra_is_sound() {
let a = set(&[Capability::LocalRead, Capability::ArbitraryEgress]);
let b = set(&[Capability::ArbitraryEgress, Capability::MutateExternal]);
assert!(a.contains(Capability::LocalRead));
assert!(!a.contains(Capability::MutateExternal));
assert_eq!(
a.union(b),
set(&[
Capability::LocalRead,
Capability::ArbitraryEgress,
Capability::MutateExternal
])
);
assert_eq!(a.intersection(b), set(&[Capability::ArbitraryEgress]));
assert_eq!(a.difference(b), set(&[Capability::LocalRead]));
assert!(set(&[Capability::LocalRead]).is_subset_of(a));
assert!(!a.is_subset_of(b));
assert!(CapabilitySet::EMPTY.is_empty());
assert!(CapabilitySet::EMPTY.is_subset_of(CapabilitySet::EMPTY));
for c in Capability::ALL {
assert!(
CapabilitySet::all().contains(c),
"{} missing from all()",
c.as_str()
);
}
let rebuilt: CapabilitySet = a.iter().collect();
assert_eq!(rebuilt, a);
}
#[test]
fn names_round_trip_and_unknown_names_grant_nothing() {
for c in Capability::ALL {
assert_eq!(Capability::from_name(c.as_str()), Some(c), "{}", c.as_str());
}
assert_eq!(Capability::from_name("egress"), None);
assert_eq!(Capability::from_name(""), None);
let (parsed, unknown) =
CapabilitySet::from_names(["arbitrary-egress", "definitely-not-a-capability", ""]);
assert_eq!(parsed, set(&[Capability::ArbitraryEgress]));
assert_eq!(
unknown,
vec!["definitely-not-a-capability".to_owned(), String::new()]
);
let all_names = CapabilitySet::all().names();
let (reparsed, unknown) = CapabilitySet::from_names(all_names);
assert_eq!(reparsed, CapabilitySet::all());
assert!(unknown.is_empty());
}
#[test]
fn required_capabilities_maps_annotations_and_provenance() {
let local_read = ToolProfile {
read_only: true,
..profile(ToolOrigin::LocalSandbox)
};
assert_eq!(
required_capabilities(local_read),
set(&[Capability::LocalRead])
);
let local_write = ToolProfile {
destructive: true,
..profile(ToolOrigin::LocalSandbox)
};
assert_eq!(
required_capabilities(local_write),
set(&[Capability::LocalRead, Capability::LocalWrite])
);
let web = ToolProfile {
read_only: true,
open_world: true,
..profile(ToolOrigin::Fetcher)
};
assert_eq!(
required_capabilities(web),
set(&[Capability::ArbitraryEgress])
);
let paid = ToolProfile {
destructive: true,
open_world: true,
..profile(ToolOrigin::Fetcher)
};
assert_eq!(
required_capabilities(paid),
set(&[Capability::ArbitraryEgress, Capability::MutateExternal])
);
let registered_read = ToolProfile {
read_only: true,
..profile(ToolOrigin::RegisteredConnector)
};
assert_eq!(
required_capabilities(registered_read),
set(&[Capability::FixedConnectorRead])
);
let registered_open_read = ToolProfile {
read_only: true,
open_world: true,
..profile(ToolOrigin::RegisteredConnector)
};
assert_eq!(
required_capabilities(registered_open_read),
set(&[Capability::FixedConnectorRead])
);
let registered_mutating = profile(ToolOrigin::RegisteredConnector);
assert_eq!(
required_capabilities(registered_mutating),
set(&[Capability::FixedConnectorRead, Capability::MutateExternal])
);
let registered_destructive_read = ToolProfile {
read_only: true,
destructive: true,
..profile(ToolOrigin::RegisteredConnector)
};
assert_eq!(
required_capabilities(registered_destructive_read),
set(&[Capability::FixedConnectorRead, Capability::MutateExternal])
);
let first_party_read = ToolProfile {
read_only: true,
..profile(ToolOrigin::FirstParty)
};
assert_eq!(
required_capabilities(first_party_read),
set(&[Capability::FixedConnectorRead])
);
let unknown = ToolProfile {
read_only: true, ..profile(ToolOrigin::Unknown)
};
assert_eq!(required_capabilities(unknown), CapabilitySet::all());
}
#[test]
fn profile_reads_spec_annotations() {
let s = spec("t").read_only().open_world();
let p = ToolProfile::for_spec(&s, ToolOrigin::RegisteredConnector);
assert!(p.read_only && p.open_world && !p.destructive);
assert_eq!(p.origin, ToolOrigin::RegisteredConnector);
}
#[test]
fn self_declared_hints_never_earn_taint_immunity() {
let self_declared = ToolProfile {
read_only: true,
..profile(ToolOrigin::Unknown)
};
let required = required_capabilities(self_declared);
assert!(required.contains(Capability::ArbitraryEgress));
assert!(required.contains(Capability::MutateExternal));
let granted = granted_capabilities(GrantPolicy::default(), TaintState::Tainted);
assert!(!required.is_subset_of(granted));
}
#[test]
fn taint_revokes_egress_and_external_mutation() {
let policy = GrantPolicy::default();
let clean = granted_capabilities(policy, TaintState::Clean);
assert_eq!(clean, CapabilitySet::all());
let tainted = granted_capabilities(policy, TaintState::Tainted);
assert!(!tainted.contains(Capability::ArbitraryEgress));
assert!(!tainted.contains(Capability::MutateExternal));
assert!(tainted.contains(Capability::LocalRead));
assert!(tainted.contains(Capability::LocalWrite));
assert!(tainted.contains(Capability::FixedConnectorRead));
}
#[test]
fn taint_resilient_set_survives_only_when_policy_declares_it() {
let resilient = GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: set(&[Capability::ArbitraryEgress]),
};
let granted = granted_capabilities(resilient, TaintState::Tainted);
assert!(granted.contains(Capability::ArbitraryEgress));
assert!(!granted.contains(Capability::MutateExternal));
let granted = granted_capabilities(GrantPolicy::default(), TaintState::Tainted);
assert!(!granted.contains(Capability::ArbitraryEgress));
let narrow = GrantPolicy {
base: set(&[Capability::LocalRead]),
taint_resilient: set(&[Capability::ArbitraryEgress]),
};
for taint in [TaintState::Clean, TaintState::Tainted] {
assert_eq!(
granted_capabilities(narrow, taint),
set(&[Capability::LocalRead]),
"resilience must not mint a capability the base never granted"
);
}
}
#[test]
fn grants_are_monotonic_under_every_fixed_policy() {
let all_sets = || {
(0..32u8).map(|bits| {
Capability::ALL
.into_iter()
.enumerate()
.filter(|(i, _)| bits & (1 << i) != 0)
.map(|(_, c)| c)
.collect::<CapabilitySet>()
})
};
for base in all_sets() {
for resilient in all_sets() {
let policy = GrantPolicy {
base,
taint_resilient: resilient,
};
let clean = granted_capabilities(policy, TaintState::Clean);
let tainted = granted_capabilities(policy, TaintState::Tainted);
assert!(
tainted.is_subset_of(clean),
"taint added a capability under base={:?} resilient={:?}",
base.names(),
resilient.names()
);
assert!(
tainted.is_subset_of(base),
"granted escaped the base policy under base={:?} resilient={:?}",
base.names(),
resilient.names()
);
}
}
}
#[test]
fn veto_takes_precedence_over_everything() {
let policy = CallPolicy {
veto: Some("path escapes the workspace".to_owned()),
requires_human: true,
sandbox_escalation: true,
transform: ArgTransform::Rewrite("{}".to_owned()),
};
let out = decide(
CapabilitySet::all(),
CapabilitySet::EMPTY,
&policy,
"shell_exec",
);
assert_eq!(
out,
GateOutcome::Deny("path escapes the workspace".to_owned())
);
}
#[test]
fn missing_capabilities_escalate_with_the_missing_set() {
let required = set(&[Capability::ArbitraryEgress]);
let granted = granted_capabilities(GrantPolicy::default(), TaintState::Tainted);
let out = decide(required, granted, &CallPolicy::default(), "web_fetch");
let GateOutcome::Escalate { reason, missing } = out else {
panic!("expected escalate, got {out:?}");
};
assert_eq!(missing, set(&[Capability::ArbitraryEgress]));
assert!(
reason.contains("web_fetch"),
"reason names the tool: {reason}"
);
assert!(!reason.is_empty());
}
#[test]
fn policy_gate_escalates_with_empty_missing_set_and_empty_reason() {
let policy = CallPolicy {
requires_human: true,
..CallPolicy::default()
};
let out = decide(
set(&[Capability::LocalRead]),
CapabilitySet::all(),
&policy,
"shell_exec",
);
assert_eq!(
out,
GateOutcome::Escalate {
reason: String::new(),
missing: CapabilitySet::EMPTY
}
);
let policy = CallPolicy {
sandbox_escalation: true,
..CallPolicy::default()
};
let out = decide(
set(&[Capability::LocalWrite]),
CapabilitySet::all(),
&policy,
"file_write",
);
assert!(matches!(out, GateOutcome::Escalate { missing, .. } if missing.is_empty()));
}
#[test]
fn allowed_calls_honor_the_argument_transform() {
let granted = CapabilitySet::all();
let required = set(&[Capability::LocalRead]);
let out = decide(required, granted, &CallPolicy::default(), "file_read");
assert_eq!(out, GateOutcome::Allow);
let rewrite = CallPolicy {
transform: ArgTransform::Rewrite(r#"{"path":"safe.txt"}"#.to_owned()),
..CallPolicy::default()
};
assert_eq!(
decide(required, granted, &rewrite, "file_read"),
GateOutcome::Modify(r#"{"path":"safe.txt"}"#.to_owned())
);
let inject = CallPolicy {
transform: ArgTransform::InjectContext("note".to_owned()),
..CallPolicy::default()
};
assert_eq!(
decide(required, granted, &inject, "file_read"),
GateOutcome::InjectContext("note".to_owned())
);
let out = decide(
CapabilitySet::all(),
granted.difference(TAINT_REVOKED),
&rewrite,
"t",
);
assert!(matches!(out, GateOutcome::Escalate { .. }));
}
#[test]
fn decision_table_is_exhaustive_over_required_and_granted() {
let all_sets = || {
(0..32u8).map(|bits| {
Capability::ALL
.into_iter()
.enumerate()
.filter(|(i, _)| bits & (1 << i) != 0)
.map(|(_, c)| c)
.collect::<CapabilitySet>()
})
};
for required in all_sets() {
for granted in all_sets() {
let out = decide(required, granted, &CallPolicy::default(), "t");
if required.is_subset_of(granted) {
assert_eq!(
out,
GateOutcome::Allow,
"required={:?} granted={:?}",
required.names(),
granted.names()
);
} else {
let GateOutcome::Escalate { missing, .. } = out else {
panic!(
"expected escalate for required={:?} granted={:?}",
required.names(),
granted.names()
);
};
assert_eq!(missing, required.difference(granted));
assert!(!missing.is_empty());
}
}
}
}
#[test]
fn behavior_changing_rows_are_pinned() {
let tainted_grants = granted_capabilities(GrantPolicy::default(), TaintState::Tainted);
let mutating = required_capabilities(profile(ToolOrigin::RegisteredConnector));
let out = decide(
mutating,
tainted_grants,
&CallPolicy::default(),
"send_message",
);
assert!(
matches!(out, GateOutcome::Escalate { ref missing, .. } if missing.contains(Capability::MutateExternal)),
"got {out:?}"
);
let resilient_policy = GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: set(&[Capability::ArbitraryEgress]),
};
let resilient_grants = granted_capabilities(resilient_policy, TaintState::Tainted);
let fetch = required_capabilities(ToolProfile {
read_only: true,
open_world: true,
..profile(ToolOrigin::Fetcher)
});
assert_eq!(
decide(fetch, resilient_grants, &CallPolicy::default(), "web_fetch"),
GateOutcome::Allow
);
let read = required_capabilities(ToolProfile {
read_only: true,
..profile(ToolOrigin::RegisteredConnector)
});
assert_eq!(
decide(read, tainted_grants, &CallPolicy::default(), "service_list"),
GateOutcome::Allow
);
}
#[test]
fn engine_is_uniform_over_the_taxonomy() {
for c in Capability::ALL {
let single = CapabilitySet::of(c);
let out = decide(single, CapabilitySet::EMPTY, &CallPolicy::default(), "t");
assert!(
matches!(out, GateOutcome::Escalate { missing, .. } if missing == single),
"{}",
c.as_str()
);
assert_eq!(
decide(single, single, &CallPolicy::default(), "t"),
GateOutcome::Allow,
"{}",
c.as_str()
);
}
}
#[test]
fn redeclaration_only_ever_grows_the_required_set() {
let origins = [
ToolOrigin::LocalSandbox,
ToolOrigin::Fetcher,
ToolOrigin::FirstParty,
ToolOrigin::RegisteredConnector,
ToolOrigin::Unknown,
];
let bools = [false, true];
let mut profiles = Vec::new();
for origin in origins {
for read_only in bools {
for destructive in bools {
for open_world in bools {
profiles.push(ToolProfile {
origin,
read_only,
destructive,
open_world,
});
}
}
}
}
for &old in &profiles {
for &new in &profiles {
let clamped = monotonic_redeclaration(old, new);
let before = required_capabilities(old);
let after = required_capabilities(clamped);
assert!(
before.is_subset_of(after),
"re-declaration shrank requirements: old={old:?} new={new:?} clamped={clamped:?}"
);
assert!(
!old.open_world || clamped.open_world,
"open-world never downgrades to closed-world"
);
assert!(
old.read_only || !clamped.read_only,
"a tool never gains read-only at runtime"
);
}
}
let old = ToolProfile {
read_only: true,
..profile(ToolOrigin::RegisteredConnector)
};
let new = ToolProfile {
destructive: true,
..profile(ToolOrigin::RegisteredConnector)
};
let clamped = monotonic_redeclaration(old, new);
assert!(required_capabilities(clamped).contains(Capability::MutateExternal));
}
#[test]
fn grant_access_is_never_granted_by_any_input() {
assert!(!Capability::ALL.contains(&Capability::GrantAccess));
assert!(!CapabilitySet::all().contains(Capability::GrantAccess));
for policy in [
GrantPolicy::default(),
GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: CapabilitySet::all(),
},
] {
for taint in [TaintState::Clean, TaintState::Tainted] {
assert!(
!granted_capabilities(policy, taint).contains(Capability::GrantAccess),
"grant-access must never be granted (policy={policy:?}, taint={taint:?})"
);
}
}
assert_eq!(Capability::from_name("grant-access"), None);
let (parsed, unknown) = CapabilitySet::from_names(["grant-access"]);
assert!(parsed.is_empty(), "grant-access parses to nothing");
assert_eq!(unknown, vec!["grant-access".to_owned()]);
}
#[test]
fn access_grant_always_escalates_in_every_state() {
let required = required_capabilities(ToolProfile {
read_only: false,
destructive: true,
open_world: true,
..profile(ToolOrigin::AccessGrant)
});
assert!(
required.contains(Capability::GrantAccess),
"an access-grant tool requires the never-granted marker"
);
for policy in [
GrantPolicy::default(),
GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: CapabilitySet::all(),
},
] {
for taint in [TaintState::Clean, TaintState::Tainted] {
let granted = granted_capabilities(policy, taint);
let out = decide(required, granted, &CallPolicy::default(), "invite");
assert_ne!(out, GateOutcome::Allow, "policy={policy:?} taint={taint:?}");
let GateOutcome::Escalate { reason, missing } = out else {
panic!("expected escalate for policy={policy:?} taint={taint:?}");
};
assert!(
missing.contains(Capability::GrantAccess),
"the shortfall names the access-grant marker"
);
assert!(reason.contains("invite"), "reason names the tool: {reason}");
}
}
}
#[test]
fn revoke_access_is_never_granted_by_any_input() {
assert!(!Capability::ALL.contains(&Capability::RevokeAccess));
assert!(!CapabilitySet::all().contains(Capability::RevokeAccess));
for policy in [
GrantPolicy::default(),
GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: CapabilitySet::all(),
},
] {
for taint in [TaintState::Clean, TaintState::Tainted] {
assert!(
!granted_capabilities(policy, taint).contains(Capability::RevokeAccess),
"revoke-access must never be granted (policy={policy:?}, taint={taint:?})"
);
}
}
assert_eq!(Capability::from_name("revoke-access"), None);
let (parsed, unknown) = CapabilitySet::from_names(["revoke-access"]);
assert!(parsed.is_empty(), "revoke-access parses to nothing");
assert_eq!(unknown, vec!["revoke-access".to_owned()]);
}
#[test]
fn revoke_access_always_escalates_in_every_state() {
let required = required_capabilities(ToolProfile {
read_only: false,
destructive: true,
open_world: false,
..profile(ToolOrigin::AccessRevoke)
});
assert!(
required.contains(Capability::RevokeAccess),
"an access-revoke tool requires the never-granted marker"
);
for policy in [
GrantPolicy::default(),
GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: CapabilitySet::all(),
},
] {
for taint in [TaintState::Clean, TaintState::Tainted] {
let granted = granted_capabilities(policy, taint);
let out = decide(required, granted, &CallPolicy::default(), "revoke");
assert_ne!(out, GateOutcome::Allow, "policy={policy:?} taint={taint:?}");
let GateOutcome::Escalate { reason, missing } = out else {
panic!("expected escalate for policy={policy:?} taint={taint:?}");
};
assert!(
missing.contains(Capability::RevokeAccess),
"the shortfall names the revoke-access marker"
);
assert!(reason.contains("revoke"), "reason names the tool: {reason}");
}
}
}
#[test]
fn manage_admin_is_never_granted_by_any_input() {
assert!(!Capability::ALL.contains(&Capability::ManageAdmin));
assert!(!CapabilitySet::all().contains(Capability::ManageAdmin));
for policy in [
GrantPolicy::default(),
GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: CapabilitySet::all(),
},
] {
for taint in [TaintState::Clean, TaintState::Tainted] {
assert!(
!granted_capabilities(policy, taint).contains(Capability::ManageAdmin),
"manage-admin must never be granted (policy={policy:?}, taint={taint:?})"
);
}
}
assert_eq!(Capability::from_name("manage-admin"), None);
let (parsed, unknown) = CapabilitySet::from_names(["manage-admin"]);
assert!(parsed.is_empty(), "manage-admin parses to nothing");
assert_eq!(unknown, vec!["manage-admin".to_owned()]);
}
#[test]
fn manage_admin_always_escalates_in_every_state() {
let required = required_capabilities(ToolProfile {
read_only: false,
destructive: true,
open_world: false,
..profile(ToolOrigin::AdminManage)
});
assert!(
required.contains(Capability::ManageAdmin),
"an admin-manage tool requires the never-granted marker"
);
for policy in [
GrantPolicy::default(),
GrantPolicy {
base: CapabilitySet::all(),
taint_resilient: CapabilitySet::all(),
},
] {
for taint in [TaintState::Clean, TaintState::Tainted] {
let granted = granted_capabilities(policy, taint);
let out = decide(required, granted, &CallPolicy::default(), "demote");
assert_ne!(out, GateOutcome::Allow, "policy={policy:?} taint={taint:?}");
let GateOutcome::Escalate { reason, missing } = out else {
panic!("expected escalate for policy={policy:?} taint={taint:?}");
};
assert!(
missing.contains(Capability::ManageAdmin),
"the shortfall names the manage-admin marker"
);
assert!(reason.contains("demote"), "reason names the tool: {reason}");
}
}
}
#[test]
fn escalation_reason_is_plain_language_on_every_shape() {
let shapes = [
set(&[Capability::ArbitraryEgress]),
set(&[Capability::MutateExternal]),
set(&[Capability::ArbitraryEgress, Capability::MutateExternal]),
set(&[Capability::LocalWrite]),
set(&[Capability::GrantAccess]),
];
for missing in shapes {
let reason = escalation_reason("my_tool", missing);
assert!(reason.contains("my_tool"), "{reason}");
for banned in [
"capability",
"trifecta",
"egress",
"exfiltrat",
"taint",
"please",
"sorry",
"unfortunately",
] {
assert!(
!reason.to_lowercase().contains(banned),
"banned word {banned:?} in {reason:?}"
);
}
}
assert_eq!(GateOutcome::Allow.label(), "allow");
assert_eq!(
GateOutcome::Escalate {
reason: String::new(),
missing: CapabilitySet::EMPTY
}
.label(),
"escalate"
);
assert_eq!(GateOutcome::Deny(String::new()).label(), "deny");
assert_eq!(GateOutcome::Modify(String::new()).label(), "modify");
assert_eq!(
GateOutcome::InjectContext(String::new()).label(),
"inject_context"
);
}