use super::*;
#[test]
fn a_deny_hidden_past_a_classifier_limit_needs_review() {
let p = Policy::parse(crate::DEFAULT_POLICY).unwrap();
let operands = (0..crate::shell::MAX_ATOMS + 10)
.map(|i| format!("f{i}"))
.collect::<Vec<_>>()
.join(" ");
for line in [
format!("cat {operands}; cat ~/.ssh/id_rsa"),
format!(
"cat ~/.ssh/id_rsa # {}",
"x".repeat(crate::lexer::MAX_COMMAND_BYTES)
),
] {
let d = evaluate(&p, &ctx(), &shell(&line)).unwrap();
assert!(
matches!(d.verdict, Verdict::Review | Verdict::Deny),
"{:?} {:?}",
d.verdict,
d.rules
);
}
}
#[test]
fn a_deny_hidden_past_the_glob_expansion_limit_needs_review() {
use crate::expand::MAX_MATCHES;
use crate::realpath::MapPathResolver;
let p = Policy::parse(crate::DEFAULT_POLICY).unwrap();
let compiled = CompiledPolicy::compile(&p, &ctx()).unwrap();
let mut files: Vec<String> = (0..=MAX_MATCHES).map(|n| format!("/p/a{n}")).collect();
files.push("/p/z/.env".to_owned());
let fs = MapPathResolver {
files,
..MapPathResolver::default()
};
for line in ["cat */.env", "cat *", "true; cat */.env"] {
let d = compiled.decide_with(&shell(line), &NoResolver, &fs);
assert!(
matches!(d.verdict, Verdict::Review | Verdict::Deny),
"{line}: {:?} {:?}",
d.verdict,
d.rules
);
}
}
fn nested(levels: usize, inner: &str) -> String {
format!("{}{inner}{}", "echo $(".repeat(levels), ")".repeat(levels))
}
#[test]
fn nesting_at_the_depth_limit_is_classified_and_past_it_needs_review() {
let p = Policy::parse(crate::DEFAULT_POLICY).unwrap();
let decide = |line: &str| evaluate(&p, &ctx(), &shell(line)).unwrap();
let at = usize::from(crate::shell::MAX_DEPTH);
let d = decide(&nested(at, "cat src/main.rs"));
assert_eq!(d.verdict, Verdict::Allow, "{:?}", d.rules);
let d = decide(&nested(at, "cat ~/.ssh/id_rsa"));
assert_eq!(d.verdict, Verdict::Deny, "{:?}", d.rules);
for inner in ["cat src/main.rs", "cat ~/.ssh/id_rsa"] {
let d = decide(&nested(at + 1, inner));
assert!(
matches!(d.verdict, Verdict::Review | Verdict::Deny),
"{inner}: {:?} {:?}",
d.verdict,
d.rules
);
}
let d = decide(&nested(at + 1, "cat src/main.rs"));
assert_eq!(
(d.verdict, d.rules),
(Verdict::Review, vec!["unparseable".to_owned()])
);
}
#[test]
fn braces_at_the_word_limit_are_classified_and_past_it_need_review() {
let p = Policy::parse(crate::DEFAULT_POLICY).unwrap();
let decide = |line: &str| evaluate(&p, &ctx(), &shell(line)).unwrap();
let at = crate::lexer::MAX_BRACE_WORDS;
let d = decide(&format!("cat src/f{{1..{at}}}.rs"));
assert_eq!(d.verdict, Verdict::Allow, "{:?}", d.rules);
let d = decide(&format!("cat {{src/f{{1..{}}}.rs,.env}}", at - 1));
assert_eq!(d.verdict, Verdict::Deny, "{:?}", d.rules);
let past = at + 1;
let d = decide(&format!("cat src/f{{1..{past}}}.rs"));
assert_eq!(
(d.verdict, d.rules),
(Verdict::Review, vec!["unparseable".to_owned()])
);
let d = decide(&format!("cat src/f{{1..{past}}}.rs; cat .e{{nv,x}}"));
assert!(
matches!(d.verdict, Verdict::Review | Verdict::Deny),
"{:?} {:?}",
d.verdict,
d.rules
);
}
#[test]
fn unparseable_is_never_allowed() {
let p = policy("version: 1\ndefaults: allow\n");
let d = evaluate(&p, &ctx(), &shell("echo 'unterminated")).unwrap();
assert_eq!(d.verdict, Verdict::Review);
assert_eq!(d.rules, ["unparseable"]);
let d = evaluate(
&p,
&ctx(),
&Action::Net {
url: "https://".into(),
},
)
.unwrap();
assert_eq!(d.verdict, Verdict::Defer);
let powershell = Action::ForeignShell {
shell: "PowerShell".into(),
command: "Get-ChildItem".into(),
};
let d = evaluate(&p, &ctx(), &powershell).unwrap();
assert_eq!(
(d.verdict, d.rules),
(Verdict::Defer, vec!["unparseable".to_owned()])
);
let p = policy("version: 1\ndefaults: review\n");
let d = evaluate(&p, &ctx(), &shell("echo 'unterminated")).unwrap();
assert_eq!(d.verdict, Verdict::Review);
}