test(hooks): characterize the six remaining guard hooks#31
Merged
Conversation
Until the previous commit no test in this repo had ever executed a hook -- the suite covered the transform layer only. That is how protect-generated-dirs spent three minor versions guarding the wrong set of directories. These pin what the six other guards actually do, so a later edit to a regex or a pathspec parser cannot silently widen or narrow one: - block-no-verify catches --no-verify, -n, --no-gpg-sign, and `-c commit.gpgsign=false`, but not the word --no-verify inside an echo. - block-force-push blocks main and master including --force-with-lease, and deliberately allows force-pushing a feature branch. - guard-dangerous-bash blocks /, /*, ~, $HOME, chmod -R 777 /, curl | sh, dd of=/dev/sd*, mkfs, and the fork bomb -- and deliberately allows `sudo rm -rf /var` and `rm -rf ./build`. Its header calls the list "intentionally narrow"; widening it is a design change, not a bug fix, and the test now says so out loud. - protect-lockfile-edit covers twelve ecosystems. - secret-scan-on-edit rejects an AWS key id, a GitHub PAT, and a private-key header, reads an Edit's new_string as well as a Write's content, and allows both a process.env reference and a placeholder in .env.example. - block-secret-file-stage resolves pathspecs by asking git, so it is exercised against a real temporary repository: an explicit `git add .env`, the sweeping `git add .` and `-A`, `--dry-run` staging nothing, and a gitignored file ceasing to be flagged. Every hook is asserted to fail open on empty, malformed, and unexpected payloads. A hook that throws returns non-zero and blocks every tool call in the session, so failing open is the contract, not an accident. Verified the tests are not tautological: flipping one `process.exit(2)` to `exit(0)` in block-force-push fails exactly 3 of them, and restoring it passes. Suite: 143 -> 239 tests. README count and file list updated.
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.
Why
Before #30, no test in this repo had ever executed a hook. The suite covered the transform layer only. That is precisely how
protect-generated-dirsspent three minor versions guarding the wrong set of directories without anyone noticing.#30 tested the one hook with a defect. This tests the other six, so a later edit to a regex or a pathspec parser cannot silently widen or narrow a guard.
What is pinned
block-no-verify--no-verify,-n,--no-gpg-sign,-c commit.gpgsign=falseecho --no-verify(not a git call)block-force-pushmain/master, incl.--force-with-leaseguard-dangerous-bash/,/*,~,$HOME,chmod -R 777 /,curl | sh,dd of=/dev/sd*,mkfs, fork bombsudo rm -rf /var,rm -rf ./build,git reset --hardprotect-lockfile-editpackage.json, source filessecret-scan-on-editnew_stringandcontentprocess.env.API_KEY, a placeholder in.env.exampleblock-secret-file-stagegit add .env, the sweepinggit add ./-A--dry-run,.env.example, a gitignored fileTwo of those "allows" deserve saying out loud rather than treating as bugs:
guard-dangerous-bashdoes not blocksudo rm -rf /var. Its header calls the list "intentionally narrow: unambiguously dangerous shapes, not commands that touch files."rm -rf /varis routine inside a container. Widening the list is a design change, not a bug fix — the test now documents that boundary instead of leaving the next reader to guess.block-force-pushpermits force-pushing a feature branch. That's the author's own history to rewrite.block-secret-file-stageresolves pathspecs by asking git itself, so testing it against a fake payload only tests the parser. It's exercised against a real temporary git repository holding a real.envandserver.key.Fail-open is the contract
Every hook is asserted to exit
0on empty stdin, malformed JSON,{}, a missingtool_input, and a nullcommand/file_path. A hook that throws returns non-zero — which blocks every tool call in the session. Failing open is the contract, not an accident. That's 42 of the assertions here.These tests are not tautological
Mutation-checked. Flip one
process.exit(2)toexit(0)inblock-force-push:Verification
All seven hooks now have coverage. Only
README.mdand the new test file change — no source or generated output touched.