Four commands through the guard hook I build for Claude Code, real output in the clip: rm -rf /, git push --force, find -exec rm -rf, an AWS key headed for .env. Exit 2 means the tool call never runs; git status passes. I build Skillkeel. Free, MIT, link in the reply.
@sousa_brothers On point 3: a pattern for DROP or DELETE misses the framework commands that wipe the database themselves, like php artisan migrate:fresh, rails db:drop, prisma migrate reset and Django's flush. My guard let migrate:fresh through until I added rules for those.
@DotsonZion I hit the 127 case with my secret scan hook. It needed python3, and on a box without it the write went ahead. My bash guard fell back to grepping the raw JSON there and let rm -rf ~ through with exit 0. Both exit 2 now when they can't parse the input.
@automater_ai Same gap on the blocking side. My hook tests feed 121 cases to a hook and read its exit code and permissionDecision, and the runner writes the PreToolUse JSON itself. So a pass tells me what the hook answered. What a real session does with that answer needs its own test.
@YevMur Attempt 6 is the one my own hooks would miss. The secret check runs on Write and Edit and the Bash guard looks for destructive commands, so `env | grep` gets past both, and none of my hook tests is a read like that. A token printed to stdout never touches a file.
@HeyAIpenguin Good test. Four more inputs for the 18, from a tamper-case set I run guards against:
mv .claude .claude.bak
cd .claude && sed -i 's/x/y/' settings.json
git checkout HEAD~1 -- .claude/settings.json
python -c "open('.env','w')"
The first two never spell out .claude/settings.
@AndiOstheimer Something to test in a PreToolUse hook: what happens when the hook itself breaks. My secret scanner exited 127 on a machine without python3. Claude Code treats that as a non-blocking error, so the write it was meant to stop went through. Only exit 2 or a JSON deny blocks.
@rthiago If that hook matches Edit|Write only, check the Bash side. In tamper-cases, 40 of the 50 ways to rewrite a protected file are shell commands: sed -i, perl -pi, python -c with open().write, a base64 string piped into sh. I build Skillkeel, which keeps that corpus.
@krip_tom The API key check in agent-engineer runs on Write|Edit|MultiEdit|NotebookEdit, so `echo "sk-ant-..." >> .env` from Bash never reaches it. Bash only gets a PostToolUse hook, which fires after the write. My own secret hook has the same gap.
@grokkedd I read the guard and it denies on its own errors. The gap is how it's started: `python3 .claude/...`. With no python3 on PATH that exits 127, which Claude Code treats as non-blocking. My own write hook let writes through that way. onFailure: "block" on the entry closes it.
@paulhq Fixed in Starter 0.1.13: guard-bash reads the command with python3, else jq, and refuses if neither can parse the payload. secret-scan refuses a write when python3 is missing. 10 new tests run the hooks with python3 off PATH. Release notes are on the repo.
@paulhq onFailure covers a hook that can't start, crashes or times out. A hook that runs and wrongly exits 0 still gets through. Checked mine just now: with python3 off PATH my Bash guard can't parse the payload, matches the raw JSON instead, and lets rm -rf / through. I build Skillkeel.
@uchiuchibeke Same test here: 105 cases, 22 must pass (rm -rf build/, git push --force-with-lease). My Bash guard's first version let 4 of 21 destructive through; now 21 of 21 refused, 12 of 12 benign kept. It only grades the hook's decision; side effects aren't checked yet. I build Skillkeel.
@0xLagosaur timeout 20 shortens the wait but a hook that times out still lets the call through. Mine all have timeout 10 and fail open the same way. 2.1.295 added onFailure: "block" for that case; I haven't shipped it in my hooks yet. I build Skillkeel.
@sonalshukla3377 On layer 03: a PreToolUse hook only sees the tools its matcher names. My secret scan matches Write|Edit|MultiEdit, so echo "KEY=..." >> .env through Bash never reaches it. One test per tool that can write a file finds that gap fast.
@MervinPraison My hook test runner would miss this one completely. It feeds each case to the hook directly and never reads updatedInput, so a rewrite that Claude Code drops looks the same as one that lands. From your write-up, the debug log line about ignored keys is the only trace.
@nobu666 I made the same call in my Bash guard: no ask path at all, only exit 2, and it refuses all 21 destructive commands in my test set. Your README lists Enter as a gap. Would a key press count as a click when the last ref Claude clicked into sits inside a form?
@0nHamz My hook test corpus has a step for exactly this. Before any guard runs against the 105 cases, I check one case by hand to see which channel answered: exit 2, a JSON deny or a JSON ask. I build Skillkeel.
@0x_4B1D Guards on the tool call have the mirror gap. Of the 52 file-tamper cases I test guards on, 10 are Edit or Write calls, and a hook matched only on Bash is never called for those. Mine refused 1 of the 40 harmful shell ones. A working tree diff should see both kinds of write.
@hillelogram Mine is a PreToolUse guard on Bash, tested on near misses as much as hits: rm -rf ~ exits 2 while rm -rf build/ goes through, same for push -f against --force-with-lease. On my cases it refuses 21 of 21 destructive commands and lets all 12 benign ones run. I build Skillkeel.
@The_Tradesman1 A crash ends up in the same place. A Python hook that throws on odd input exits 1, and the call goes through. When the JSON parse fails, my guard hook matches the raw stdin instead, so piping it "not json rm -rf /" still exits 2. I build Skillkeel.