Skip to content

fix(safety-gate): close the find delete-verb gap#262

Merged
tieubao merged 2 commits into
masterfrom
fix/find-delete-gate
Jul 20, 2026
Merged

fix(safety-gate): close the find delete-verb gap#262
tieubao merged 2 commits into
masterfrom
fix/find-delete-gate

Conversation

@tieubao

@tieubao tieubao commented Jul 17, 2026

Copy link
Copy Markdown
Member

What

Adds a find arm to safety-gate.sh so deletes expressed through find are gated the same way rm -rf already was, and extracts the build-artifact allowlist into targets_all_safe() so both verbs share one definition of "regenerable".

Why

Not hypothetical. On 2026-07-08 a disk-cleanup batch ran this, and the gate did not fire:

find <bun-global-root> -mindepth 1 -delete

The target was ~/.cache/.bun. It reads like a cache; it was actually the bun global install root, holding install/global/node_modules/ and the bin/ symlinks. It wiped the omp (oh-my-pi) cockpit. Nobody noticed until 2026-07-17, when omp update came back "command not found".

The gate is parse-aware and keys each rule on its segment's binary (SPEC-064). It had arms for rm, git and kubectl. That segment's binary was find, so it matched nothing and passed. xargs rm -rf was already covered (the wrapper-strip loop reduces it to rm); find was the hole.

How

  • -delete, or -exec/-execdir/-ok/-okdir paired with an rm token, marks the segment as a delete.
  • find's path operands (leading tokens before the first primary) must all be allowlisted, else block.
  • Fail-closed: a find with no path operand blocks.
  • The rm arm's behavior is unchanged; it now calls the shared helper.

Proof

docs/verification/find-delete-gate.md, with a runnable negative control at docs/verification/find-delete-gate-negctl.sh.

Run Result
tests/test-hooks.sh before 469/471, exactly D1+D2 fail (gap reproduced)
tests/test-hooks.sh after 471/471
negative control incident cmd flips 2 -> 0 -> 2 as the hook is reverted and restored; rm -rf control stays blocked throughout

D1-D5 are pinned as permanent regression tests.

Reviewer notes

Two things worth a look:

  1. The reproduce script keeps its payloads inside the file, not on the command line, because the live gate otherwise blocks the harness that is testing it. Same reason the commit messages went through -F.
  2. The per-step sha print is load-bearing. My first negative control used git stash on an already-committed clean file. It was a no-op, never reverted, and produced a false PASS. The sha caught it. Left in on purpose.

Known residual (deliberately not closed)

The allowlist still treats a bare relative .cache as regenerable, so rm -rf .cache is permitted, which from $HOME is the same class of mistake by a different route. The entry exists for project-local .cache build dirs, so narrowing it is a separate call with its own false-positive cost. Flagged rather than silently changed.

tieubao added 2 commits July 17, 2026 20:41
The gate keys each rule on its segment's binary and had arms for rm, git
and kubectl but none for find, so every delete expressed through find
passed unchallenged. On 2026-07-08 a cleanup batch ran
`find <bun-global-root> -mindepth 1 -delete` against a path that merely
looked like a cache and wiped a bun global install (the omp cockpit).
The rm rule never saw it: that segment's binary was find, not rm.

Adds a find arm covering -delete and -exec/-execdir/-ok/-okdir with rm,
and extracts the build-artifact allowlist into targets_all_safe() so rm
and find share one definition of "regenerable". Fail-closed: a find with
no path operand blocks.

Pinned as D1-D5 in tests/test-hooks.sh. Red before: 469/471 with D1+D2
failing. Green after: 471/471.
Red baseline 469/471 (D1+D2 fail), green 471/471, plus a negative control
that reverts the hook to its pre-fix blob and shows the incident command
flip 2 -> 0 -> 2 while the rm control stays blocked throughout.

Ships the control as a runnable script: its payloads live in the file
rather than on the command line, because the live gate otherwise fires on
the harness testing it. The per-step sha print is load-bearing, a first
attempt stashed an already-clean file, silently did not revert, and
produced a false PASS.
@tieubao
tieubao merged commit d2f6458 into master Jul 20, 2026
2 checks passed
@tieubao
tieubao deleted the fix/find-delete-gate branch July 20, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant