command_start misses several launcher forms, so the wrapped command is never scanned
Same family as #146/#147/#151, but outside env/bash -c: these are plain
launcher prefixes in front of a destructive command, not nested payloads. Probed
against hooks/block-rm-rf.py (rc=2 blocked, rc=0 allowed).
Launchers not recognized at all
rc=0 watch rm -rf /tmp/x
rc=0 script rm -rf /tmp/x
rc=0 chronic rm -rf /tmp/x
rc=2 stdbuf rm -rf /tmp/x # control: recognized
watch, script, and chronic (moreutils) each run their argument as a
command. None is in the launcher set.
Recognized launcher, but an option argument stops command_start short
rc=2 sudo rm -rf /tmp/x
rc=0 sudo -u root rm -rf /tmp/x
rc=0 sudo -E rm -rf /tmp/x
rc=2 ionice rm -rf /tmp/x
rc=0 ionice -c3 rm -rf /tmp/x
sudo and ionice are bare-skip launchers with no option grammar, so
command_start stops at the first flag — the #147 shape, for launchers other
than env. nice -n 10 and xargs -0 are handled, so the mechanism exists;
these two are missing from _LAUNCHER_VALUE_FLAGS.
find -exec is a launcher and is never unwrapped
rc=0 find . -name node_modules -exec rm -rf {} +
rc=0 find . -name node_modules -execdir rm -rf {} +
-exec/-execdir take an argv up to ; or +. This is a distinct mechanism
from a leading launcher prefix: the command sits mid-argv, not at
command_start.
Backstop
permissions.deny carries Bash(sudo *), so the sudo rows above are still
denied at the permission layer. watch, script, chronic, ionice -c3, and
find -exec have no deny-rule backstop — Bash(rm -rf *) only matches at the
start of the command.
Fix direction
- Add
watch, script, chronic to the launcher set.
- Give
sudo (-u, -g, -U, -C, -h, -p, -r, -t, plus valueless
-E, -H, -n, -S, --) and ionice (-c, -n, -p, including the
attached -c3 form) entries in _LAUNCHER_VALUE_FLAGS.
- Treat the
-exec/-execdir argv slice in a find segment as a nested
payload, terminated by ; or +, so iter_segments recurses into it.
- Regression cases in
tests/test-cmdscan.sh and block assertions in
tests/test-guard-hooks.sh for each row above.
Where this came from
Hit while hardening the per-repo block-dangerous.sh guard in
millsmillsymills/research-template (PR #90), which had to solve the same
command-position problem in shell. Probing the Python guards for the equivalent
cases turned up the rows above. The prose false-positive side of that work does
not reproduce here — cmdscan tokenizes, so a commit message or PR body naming
a destructive command is correctly allowed.
command_startmisses several launcher forms, so the wrapped command is never scannedSame family as #146/#147/#151, but outside
env/bash -c: these are plainlauncher prefixes in front of a destructive command, not nested payloads. Probed
against
hooks/block-rm-rf.py(rc=2 blocked, rc=0 allowed).Launchers not recognized at all
watch,script, andchronic(moreutils) each run their argument as acommand. None is in the launcher set.
Recognized launcher, but an option argument stops
command_startshortsudoandioniceare bare-skip launchers with no option grammar, socommand_startstops at the first flag — the #147 shape, for launchers otherthan
env.nice -n 10andxargs -0are handled, so the mechanism exists;these two are missing from
_LAUNCHER_VALUE_FLAGS.find -execis a launcher and is never unwrapped-exec/-execdirtake an argv up to;or+. This is a distinct mechanismfrom a leading launcher prefix: the command sits mid-argv, not at
command_start.Backstop
permissions.denycarriesBash(sudo *), so thesudorows above are stilldenied at the permission layer.
watch,script,chronic,ionice -c3, andfind -exechave no deny-rule backstop —Bash(rm -rf *)only matches at thestart of the command.
Fix direction
watch,script,chronicto the launcher set.sudo(-u,-g,-U,-C,-h,-p,-r,-t, plus valueless-E,-H,-n,-S,--) andionice(-c,-n,-p, including theattached
-c3form) entries in_LAUNCHER_VALUE_FLAGS.-exec/-execdirargv slice in afindsegment as a nestedpayload, terminated by
;or+, soiter_segmentsrecurses into it.tests/test-cmdscan.shand block assertions intests/test-guard-hooks.shfor each row above.Where this came from
Hit while hardening the per-repo
block-dangerous.shguard inmillsmillsymills/research-template(PR #90), which had to solve the samecommand-position problem in shell. Probing the Python guards for the equivalent
cases turned up the rows above. The prose false-positive side of that work does
not reproduce here —
cmdscantokenizes, so a commit message or PR body naminga destructive command is correctly allowed.