Summary
install/hooks/lib/safety-classifier.ts classifyCommand decides allow from the first word on several paths (SEARCH_TOOLS, loopback curl, the for/while/until loop allow, DEV_BINARIES, READ_ONLY_COMMAND_PATTERNS, and bashTargetsTrustedPath, which tests the whole command string). Later segments of a compound command (;, &&, |) and output redirections are not checked, so a write that does not match a named shape is allowed. Safety.hook.ts emits allow, which skips the native prompt for the whole command.
Your own comment above the destructive-shape scan says the same thing: these paths "only look at the head of the command". The shape denies (mutating-pipe-consumer, dangerous-shape) catch some chains but not the rest.
Evidence
Replayed 7 Oct 2026 against LifeOS/install/hooks/lib/safety-classifier.ts at 5e2f2e8 (bun, classifyCommand({toolName:"Bash",command})):
"cat a | tee /etc/hosts" -> allow (read-only-command)
"ls > ~/.zshrc" -> allow (read-only-command)
"git status; touch ~/zz" -> allow (read-only-command)
"for i in 1; do rm ~/x; done" -> allow (shell-loop-data-iteration)
"curl http://localhost:31337/x -o ~/.zshrc" -> allow (loopback-http)
"rm ~/Documents/zz; ls /tmp" -> allow (trusted-workspace-command)
For contrast, ls; rm -rf ~/Documents/zz, curl http://localhost:31337/n; rm ~/x and npm test; rm ~/Documents/zz already come back neutral, so the shape denies cover some of this.
Impact
Allow-to-prompt only. This is a hardening report, not a hard hole: the worst case of fixing it is extra prompts. The cost of leaving it is that the allow removes the native prompt for writes the classifier never looked at.
Fix direction
Split the command on top-level ;, &&, ||, |, |&, & and newlines, respecting quotes, backslashes, $(…) and backticks, and treat an unsplittable command as not allowed. Then:
- the first-word allows fire only when every segment is itself read-only;
- an output redirect to anything other than
/dev/null or a file descriptor makes a segment non-read-only;
- the loop allow requires a read-only body, segment by segment;
- loopback
curl requires every segment to be a loopback fetch or read-only, and refuses -o, -O, -T, --output;
- the trusted-path allow applies per segment, so a
/tmp mention in one segment does not trust the rest.
I have this working in a private fork (splitter, segment check and tests, 112 passing) and can share the approach or the test cases if useful.
Summary
install/hooks/lib/safety-classifier.tsclassifyCommanddecides allow from the first word on several paths (SEARCH_TOOLS, loopbackcurl, thefor/while/untilloop allow,DEV_BINARIES,READ_ONLY_COMMAND_PATTERNS, andbashTargetsTrustedPath, which tests the whole command string). Later segments of a compound command (;,&&,|) and output redirections are not checked, so a write that does not match a named shape is allowed.Safety.hook.tsemitsallow, which skips the native prompt for the whole command.Your own comment above the destructive-shape scan says the same thing: these paths "only look at the head of the command". The shape denies (
mutating-pipe-consumer,dangerous-shape) catch some chains but not the rest.Evidence
Replayed 7 Oct 2026 against
LifeOS/install/hooks/lib/safety-classifier.tsat5e2f2e8(bun,classifyCommand({toolName:"Bash",command})):For contrast,
ls; rm -rf ~/Documents/zz,curl http://localhost:31337/n; rm ~/xandnpm test; rm ~/Documents/zzalready come backneutral, so the shape denies cover some of this.Impact
Allow-to-prompt only. This is a hardening report, not a hard hole: the worst case of fixing it is extra prompts. The cost of leaving it is that the allow removes the native prompt for writes the classifier never looked at.
Fix direction
Split the command on top-level
;,&&,||,|,|&,&and newlines, respecting quotes, backslashes,$(…)and backticks, and treat an unsplittable command as not allowed. Then:/dev/nullor a file descriptor makes a segment non-read-only;curlrequires every segment to be a loopback fetch or read-only, and refuses-o,-O,-T,--output;/tmpmention in one segment does not trust the rest.I have this working in a private fork (splitter, segment check and tests, 112 passing) and can share the approach or the test cases if useful.