Skip to content

guard hooks: watch/script/chronic, sudo+ionice option args, and find -exec hide the command from cmdscan #166

Description

@millsmillsymills

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghardeningHardening / postureneeds-triageMaintainer needs to evaluate

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions