use super::*;
fn fetch(url: &str) -> Action {
Action::Fetch {
url: url.to_owned(),
}
}
fn verdict_and_rules(p: &Policy, action: &Action) -> (Verdict, Vec<String>) {
let d = evaluate(p, &ctx(), action).unwrap();
(d.verdict, d.rules)
}
#[test]
fn fetch_is_a_narrower_kind_of_net() {
let p = policy(
"version: 1\ndefaults: { net: deny, fetch: ask }\n\
deny:\n - id: metadata\n net: ['169.254.169.254']\n\
allow:\n - id: registries\n net: ['crates.io']\n - id: docs\n fetch: ['docs.rs']\n\
ask:\n - id: local\n net: ['localhost']\n",
);
let cases = [
(
fetch("http://169.254.169.254/latest"),
Verdict::Deny,
"metadata",
),
(fetch("https://CRATES.io/x"), Verdict::Allow, "registries"),
(fetch("https://docs.rs/serde"), Verdict::Allow, "docs"),
(fetch("http://localhost:3000/"), Verdict::Ask, "local"),
(
fetch("https://unknown.example/"),
Verdict::Ask,
"default.fetch",
),
(
shell("curl -q https://docs.rs/serde"),
Verdict::Ask,
"default",
),
(
shell("curl -sSL -o serde.html https://docs.rs/serde"),
Verdict::Ask,
"default",
),
(
shell("curl -d x=1 https://docs.rs/serde"),
Verdict::Deny,
"default.net",
),
(
shell("curl -T f https://unknown.example/"),
Verdict::Deny,
"default.net",
),
];
for (action, verdict, rule) in cases {
let got = verdict_and_rules(&p, &action);
assert_eq!(got, (verdict, vec![rule.to_owned()]), "{action:?}");
}
}
#[test]
fn fetch_deny_list_never_catches_other_network_access() {
let p = policy(
"version: 1\ndefaults: allow\ndeny:\n - id: no-fetch\n fetch: ['*.example']\n\
allow:\n - id: also\n fetch: ['a.example']\n",
);
assert_eq!(
verdict_and_rules(&p, &fetch("https://a.example/")),
(Verdict::Deny, vec!["no-fetch".to_owned()]),
"deny stays absolute for fetches"
);
assert_eq!(
verdict_and_rules(&p, &shell("curl -d x=1 https://a.example/")).0,
Verdict::Allow
);
assert_eq!(
verdict_and_rules(&p, &shell("curl -q https://a.example/")).0,
Verdict::Deny
);
}
#[test]
fn fetch_defaults_fall_back_to_net() {
let p = policy("version: 1\ndefaults: { net: deny, '*': allow }\n");
assert_eq!(
verdict_and_rules(&p, &fetch("https://unknown.example/")),
(Verdict::Deny, vec!["default.net".to_owned()])
);
let p = policy("version: 1\ndefaults: allow\n");
assert_eq!(
verdict_and_rules(&p, &fetch("https://")),
(Verdict::Defer, vec!["unparseable".to_owned()])
);
}
#[test]
fn a_page_open_is_a_fetch_unless_the_policy_says_net() {
let open = |url: &str| {
let mut action = Action::mcp("mcp__playwright__browser_navigate");
if let Action::McpTool { opens, .. } = &mut action {
opens.push(url.to_owned());
}
action
};
let rules = "defaults: { '*': defer, net: deny, fetch: defer }\n\
allow:\n - id: docs\n fetch: ['docs.rs']\n";
let as_fetch = policy(&format!("version: 1\n{rules}"));
let as_net = policy(&format!(
"version: 1\nmcp: {{ browser_open: net }}\n{rules}"
));
assert!(as_fetch.browser_open_is_fetch());
assert!(!as_net.browser_open_is_fetch());
let unlisted = open("https://example.org/");
assert_eq!(verdict_and_rules(&as_fetch, &unlisted).0, Verdict::Defer);
assert_eq!(
verdict_and_rules(&as_net, &unlisted),
(Verdict::Deny, vec!["default.net".to_owned()])
);
assert_eq!(
verdict_and_rules(&as_fetch, &open("https://docs.rs/")).0,
Verdict::Defer
);
assert_eq!(
verdict_and_rules(&as_net, &open("https://docs.rs/")).0,
Verdict::Deny
);
assert!(Policy::parse("version: 1\nmcp: { browser_open: always }\n").is_err());
}