pr-gatekeeper blocked dispatch#75 five times. Every block was a real,
executable escape — twelve pathspec bypasses, arbitrary command
execution via `git -c alias`, `--upload-pack='rm …'`, a write primitive
through `--output=`, `${IFS}` word-splitting past the policy, and a path
denylist that had been a no-op in the live flow since it was written.
task check was green on all five.
The agent definition existed in no repository. A code search across the
estate found only references to it — comments in dispatch source, a
local-dev sprint doc — while the file itself lived at
~/.claude/agents/pr-gatekeeper.md on koala alone: unversioned, on one
host, one rm from gone. local-dev AGENT.md rule 8 now requires routing
control code through it, so doctrine depended on an artifact with no
history.
PORTED, PLUS WHAT THE FIVE ROUNDS ACTUALLY TAUGHT
The file already had flip-testing. Three habits that did the finding were
implied at best, and are now explicit (§4b):
- Verify by EXECUTION, not by reading the implementation. Every finding
came from running the command in a scratch repo and looking at the
filesystem. The matcher said one thing and the filesystem another,
five times.
- Read the SURVIVORS. After an allow/deny sweep, read what is still
permitted, one at a time. This caught `git commit -F /etc/passwd` —
exfiltration wearing a message flag — which neither the review nor
the differential found, because both implementations agreed and both
were wrong. Agreement is not correctness.
- Grade a tool on its EXIT CODE, never by grepping output. A crashed
linter greps as clean.
Plus rule 9 in §4: a test whose name claims more than it proves is worse
than no test, because the name wins in the reader's head.
AND THE INCIDENT THIS AGENT CAUSED (§4c)
On 2026-09-19 a probe of this agent's own making printed /proc/$PPID/environ
and `ps e` raw, and a `git credential fill` consulted the host's real
store. Seven live credentials reached the transcript and need rotation
(infra#445). The finding was correct; the method was not. Versioning the
file without that lesson would have shipped the incident along with the
agent. So: a probe that reads an environment prints a count, never the
buffer; invented sentinels only; isolated HOME for anything touching
git's credential subsystem; prove a primitive and stop.
DOCTOR
New `shadow` check: a real file in ~/.claude/agents with the same name as
a shipped agent wins over the shipped one and nothing says so. Same
silent-drift shape as the existing `outside` and `unmanaged` checks — it
works, so nothing complains, while serving content nobody updates. A
symlink is managed wiring and is not flagged.
The host-local copy on koala has been moved aside, so the shipped file is
what runs once the plugin updates.
TESTS
14 assertions, each named for what it observes rather than what it
guarantees — a grep proves a phrase is present, not that a procedure is
followed, and naming it otherwise would be the defect rule 9 exists for.
Flip-tested both directions: removing the doctor check, and altering the
probe-safety phrase, each turn the suite red.
`agents` added to task check's root-entry allowlist. Leaving it out made
check_tolerates_unverifiable_test.sh fail, which is #23's stray-entry
guard working exactly as designed — an undeclared root entry loads in no
harness and errors in none either.
Closes#51
Bump-Type: minor