#[cfg(test)]
mod tests {
use crate::command_verdict;
use crate::verdict::Verdict;
fn new_verdict(cmd: &str) -> Verdict {
command_verdict(cmd)
}
fn no_looser(comp: Verdict, reference: Verdict) -> bool {
match (comp, reference) {
(Verdict::Denied, _) => true,
(Verdict::Allowed(_), Verdict::Denied) => false,
(Verdict::Allowed(c), Verdict::Allowed(r)) => c <= r,
}
}
const COMMANDS: &[&str] = &[
"rm {P}", "rm -rf {P}", "rm -f {P}",
"cp {P} /tmp/dest", "mv {P} /tmp/dest", "ln -sf {P} /tmp/l",
"sed -i s/a/b/ {P}", "dd if={P} of=/tmp/o", "tee {P}", "truncate -s 0 {P}",
"cat {P}", "head {P}", "tail {P}", "wc {P}", "grep foo {P}", "sort {P}",
"echo {P}", "basename {P}", "dirname {P}",
];
const LOCI: &[&str] = &[
".", "./src", "src", "sub/dir",
"../..", "../sibling", "../../etc",
"/tmp", "/tmp/sub",
"~", "~/.ssh", "$HOME", "$HOME/.aws",
"/etc", "/etc/ssh", "/usr", "/", "/var/log", "/bin", "/root",
"/dev/sda", "/proc/1",
];
fn target_path(loc: &str) -> String {
format!("{}/f", loc.trim_end_matches('/'))
}
fn path_wraps(cmd_tpl: &str, loc: &str) -> Vec<(&'static str, String)> {
let braces = cmd_tpl.replace("{P}", "{}");
let direct = cmd_tpl.replace("{P}", &target_path(loc));
vec![
("find -exec", format!("find {loc} -exec {braces} \\;")),
("find -exec +", format!("find {loc} -exec {braces} +")),
("find -execdir", format!("find {loc} -execdir {braces} \\;")),
("time", format!("time {direct}")),
("timeout", format!("timeout 5 {direct}")),
("nice", format!("nice {direct}")),
("ionice", format!("ionice {direct}")),
("nohup", format!("nohup {direct}")),
("env", format!("env FOO=1 {direct}")),
("dotenv", format!("dotenv {direct}")),
("sudo", format!("sudo {direct}")),
("bash -c", format!("bash -c '{direct}'")),
("sh -c", format!("sh -c '{direct}'")),
]
}
#[test]
fn no_wrapper_launders_a_targeted_command() {
let mut violations = Vec::new();
let (mut denied_refs, mut allowed_comps, mut total) = (0u32, 0u32, 0u32);
for cmd_tpl in COMMANDS {
for loc in LOCI {
let direct = cmd_tpl.replace("{P}", &target_path(loc));
let reference = new_verdict(&direct);
if reference == Verdict::Denied {
denied_refs += 1;
}
for (label, comp_cmd) in path_wraps(cmd_tpl, loc) {
let comp = new_verdict(&comp_cmd);
total += 1;
if comp.is_allowed() {
allowed_comps += 1;
}
if !no_looser(comp, reference) {
violations.push(format!(
"[{label}] `{comp_cmd}` = {comp} > direct `{direct}` = {reference}"
));
}
}
}
}
assert!(
violations.is_empty(),
"{} composition(s) looser than operating directly on the target:\n{}",
violations.len(),
violations.join("\n")
);
assert!(
total > 3000 && denied_refs > 100 && allowed_comps > 100,
"harness went vacuous: total={total}, denied_refs={denied_refs}, allowed_comps={allowed_comps}"
);
}
#[test]
fn xargs_never_launders_stdin_items() {
let mut violations = Vec::new();
for cmd_tpl in COMMANDS {
let bare = cmd_tpl
.replace("{P}", "")
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
let reference = new_verdict(&bare);
let braces = cmd_tpl.replace("{P}", "{}");
let wraps = [
("xargs", format!("xargs {bare}")),
("xargs -I{}", format!("xargs -I{{}} {braces}")),
("piped xargs -I{}", format!("find / | xargs -I{{}} {braces}")),
];
for (label, comp_cmd) in wraps {
let comp = new_verdict(&comp_cmd);
if !no_looser(comp, reference) {
violations.push(format!(
"[{label}] `{comp_cmd}` = {comp} > bare `{bare}` = {reference}"
));
}
}
}
assert!(
violations.is_empty(),
"{} xargs composition(s) launder invisible stdin items:\n{}",
violations.len(),
violations.join("\n")
);
}
const MUST_DENY: &[&str] = &[
"sudo rm ./x",
"sudo cat ./notes.md",
"sudo cat /etc/shadow",
"sudo tee /etc/hosts",
"sudo -u root rm ./x",
"doas rm ./x",
"sudo find . -exec cat {} \\;",
"perl -e 'system(\"rm -rf /\")'",
"perl -e 'unlink glob \"*\"'",
"perl -pi -e 's/a/b/' /etc/hosts",
"perl -pi -e 's/a/b/' ~/.bashrc",
"perl -pe 's/a/b/' /etc/shadow",
"awk 'BEGIN{system(\"rm -rf /\")}'",
"ruby -e 'system(\"rm -rf /\")'",
"python3 -c 'import os; os.system(\"rm -rf /\")'",
"find / -exec rm -rf {} \\;",
"find / -print0 | xargs -0 rm -rf",
"grep -rl secret / | xargs rm",
"xargs rm",
"bash -c 'rm -rf /'",
"bash -c 'rm -rf ~'",
"eval 'rm -rf /'",
];
#[test]
fn absolute_deny_invariants_hold() {
let leaked: Vec<_> = MUST_DENY
.iter()
.filter(|cmd| new_verdict(cmd).is_allowed())
.collect();
assert!(leaked.is_empty(), "these must-deny compositions auto-approve:\n{leaked:#?}");
}
const SHOULD_ALLOW: &[&str] = &[
"find . -exec cat {} \\;",
"find ./src -exec grep foo {} \\;",
"find . -name '*.tmp' -exec rm {} \\;",
"find . -exec rm {} \\;",
"find src -exec head {} \\;",
"time rm ./stale.log",
"time cat ./notes.md",
"nice cat ./x",
"env FOO=1 cat ./x",
"xargs -I{} basename {}",
"find . | xargs cat",
"find ./src -name x | xargs grep foo",
"ls | xargs wc -l",
];
#[test]
fn legit_worktree_compositions_still_auto_approve() {
let broken: Vec<_> = SHOULD_ALLOW
.iter()
.filter(|cmd| !new_verdict(cmd).is_allowed())
.collect();
assert!(broken.is_empty(), "legit compositions are (over-)denied:\n{broken:#?}");
}
}