Conversation
pre-bash-guard.sh matched its guarded phrases anywhere in the command text, so it blocked commands that read or write *about* the thing it guards: grep -n 'Remove-Item\|rm -rf\|BLOCKED' hooks/pre-bash-guard.sh grep -rn 'git reset --hard' docs/ git commit -m "docs: explain why rm -rf is blocked" confirm -rf task None of these delete or discard anything. The first is a command that reads the guard's own source; the last is any word ending in the letters r and m followed by a flag. A guard that blocks reading and documenting itself teaches people to route around it, which is worse than not guarding. This is the same class of defect codewithmukesh#23 fixed in pre-commit-antipattern.sh — a naive grep over text that holds both code and prose about code. The same answer applies here. Four passes, in order: 1. Heredoc bodies collapse to a placeholder. A commit message written with `cat >msg <<'EOF'` is prose, and prose about a guard quotes what it guards. Delimiter lines stay, so the shape of the command is still readable. 2. Quoted spans collapse too. That is what separates a commit message carrying a phrase from a command that is one, and it reduces a grep alternation, escapes and all, to a single harmless token. 3. A shell wrapper hands its quoted argument back to a shell, so when one is present the unmasked text is scanned instead. The wrapper is itself looked for in the masked text and in command position, so prose that merely names one does not switch the scan back on. 4. A verb only counts in command position: the start of the command, or after something that begins one. This is what stops "confirm -rf". The rm allowlist reads the unmasked text, being the one check that needs the target path, and now also accepts a quoted target — "node_modules" is the same target as node_modules, and quoting is what anybody does once a path has a space. The no-jq fallback now undoes JSON string escapes. Without that a multi-line command arrives as a single line carrying literal \n, so nothing that reads the command line by line can see its shape, and the guard behaved differently with and without jq installed. Six tests added to .github/scripts/test-hooks.sh, continuing from the existing number 5. Where 5 covers a guarded phrase in another JSON field, these cover it as data inside the command itself. Five fail against the current guard. The last two are the guard rails: a recursive force delete of a project directory, and one wrapped in a shell wrapper, both still blocked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
pre-bash-guard.shmatches its guarded phrases anywhere in the command text, soit blocks commands that read or write about the thing it guards.
Every one of these is blocked today, and none of them deletes or discards
anything:
The first is a command that reads the guard's own source. The last is any word
ending in the letters
randmfollowed by a flag.I hit the first one while reading this file to understand a different block, and
the third while writing the commit for this very PR. A guard that blocks reading
and documenting itself teaches people to route around it, which is worse than
not guarding.
This is the same class of defect #23 fixed in
pre-commit-antipattern.sh— anaive grep over text that holds both code and prose about code. The same answer
applies here.
The change
Four passes, in order:
cat >msg <<'EOF'is prose, and prose about a guard quotes what it guards.Delimiter lines stay so the shape of the command is still readable.
phrase from a command that is one, and reduces a grep alternation, escapes
and all, to one harmless token.
bash -c "…"hands its argument backto a shell, so the unmasked text is scanned when one is present. The wrapper
is itself looked for in the masked text and in command position, so prose
that merely names one does not switch the scan back on.
after something that begins one. This is what stops
confirm -rf.The
rmallowlist reads the unmasked text, being the one check that needs thetarget path, and now also accepts a quoted target —
"node_modules"is the sametarget as
node_modules, and quoting is what anybody does once a path has aspace.
One adjacent fix: the no-jq fallback now undoes JSON string escapes. Without
it a multi-line command arrives as a single line carrying a literal
\n, sonothing that reads the command line by line can see its shape — the guard
behaved differently depending on whether
jqwas installed. The heredoc testbelow fails without this on a runner with no
jq.Tests
Six added to
.github/scripts/test-hooks.sh, continuing from the existing #5.Where #5 covers a guarded phrase in another JSON field, these cover it as data
inside the command itself.
Five of the six fail against the current guard — I reverted
hooks/and keptthe tests to check. The last two are the guard rails on my own escape hatch.
Separately, a 29-case matrix covering every danger shape the guard recognises —
&&,;, pipe intoxargs, the-frand-rfvspellings,bash -cwrapping, absolute paths, and all four git phrases — passes identically before
and after. Nothing that blocked before stops blocking.
Not included
No
CHANGELOG.mdentry — entries there are grouped under released versions andthere is no
Unreleasedsection, so that seemed yours to place at release time.Happy to add one if you'd rather.
🤖 Generated with Claude Code