1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
//! Composition / delegation test harness (the "put it through its paces" suite).
//!
//! Leaf-command safety is necessary but not sufficient: many commands **delegate** to a
//! nested command — `find … -exec CMD {}`, `xargs CMD`, `time CMD`, `sudo CMD`, `bash -c
//! CMD`, `env … CMD` — and the safety of the *composition* is not the safety of the leaf.
//! Making the engine authoritative changed the leaf verdicts, and those changes propagate
//! through every delegating handler.
//!
//! Rather than hand-author an expected verdict per composition, this harness derives the
//! oracle: **a wrapper must never be looser than operating directly on the target it
//! delegates to.** `find /etc -exec rm {}` must be no looser than `rm /etc/f`; `time rm
//! /etc/x` no looser than `rm /etc/x`; an `xargs` reading unknowable stdin items no looser
//! than the bare command (items invisible). The reference verdict is itself a
//! `command_verdict` call, so the full cross-product of wrappers × commands × loci is swept
//! automatically, in forced `new` mode — a composition that ALLOWS what its direct form
//! DENIES is laundering, and fails the sweep. This is what caught the `find -exec {}` →
//! system-delete and `xargs -I` → laundered-destroy bugs.
#[cfg(test)]
mod tests {
use crate::command_verdict;
use crate::verdict::Verdict;
/// Verdict with the engine authoritative (now the default and only path).
fn new_verdict(cmd: &str) -> Verdict {
command_verdict(cmd)
}
/// True when `comp` grants no more than `reference` — wrapping never loosened. A
/// composition that allows what its direct form denies is laundering.
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,
}
}
/// Inner commands, each with a `{P}` path placeholder: destroys, writes, reads, inert.
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}",
];
/// Loci spanning worktree → worktree-escape → temp → home → system → device/kernel.
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('/'))
}
/// Path-projecting wrappers: the composition operates on a path AT `loc`. Whether the
/// wrapper binds a placeholder (`find`) or passes the path through (`time`/`env`/…), the
/// honest reference is the inner command run directly on a path at `loc`. `(label, comp)`.
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")
);
// Non-vacuity: the property is only meaningful if the grid actually spans both denied
// targets (system loci) and allowed compositions (worktree), across a wide sweep.
assert!(
total > 3000 && denied_refs > 100 && allowed_comps > 100,
"harness went vacuous: total={total}, denied_refs={denied_refs}, allowed_comps={allowed_comps}"
);
}
/// `xargs` injects stdin items as the inner command's OPERANDS, whose locus is the pipe
/// source's (bound by the pipeline walker; unpinnable when the source is unknown). So xargs is
/// never LOOSER than the bare inner command — a file-reading inner with no known source
/// worst-cases to deny, and the `-I` replacement string can't smuggle in a fake worktree
/// operand. (Its precise flow-aware behavior is pinned in `tests::operand_injection_*`.)
#[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")
);
}
/// Absolute-deny invariants that do NOT reduce to the non-laundering property above: an
/// authority escalation must deny even when its inner command is worktree-safe (`sudo rm
/// ./x` is not "no looser than `rm ./x`" — it must simply deny), and an interpreter
/// payload that executes (HP-7) is opaque to the flag parser. These are checked
/// explicitly, not derived.
const MUST_DENY: &[&str] = &[
// authority escalation — runs the nested command as another user; never auto-approve,
// regardless of how benign the inner command's target looks.
"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 {} \\;",
// interpreter payloads that execute shell commands — the mini-language is opaque.
"perl -e 'system(\"rm -rf /\")'",
"perl -e 'unlink glob \"*\"'",
// `-i` rewrites each operand in place. A worktree file is the ordinary refactor and is
// admitted; what must never pass is the same edit aimed out of the workspace.
"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 /\")'",
// unbounded / iconic destroyers that must never slip through a wrapper.
"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:#?}");
}
/// Over-denial floor: the non-laundering property only guards the safety direction, so a
/// blunt "deny all delegation" would pass it. These legit worktree compositions must keep
/// auto-approving.
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 {}",
// flow-aware: a workspace-bounded pipe source keeps a file-reading inner allowed
// (a BARE `xargs cat` — no source — now correctly denies; see the shell handler tests).
"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:#?}");
}
}