Skip to content

test(hooks): characterize the six remaining guard hooks#31

Merged
MANVENDRA-github merged 1 commit into
mainfrom
test/characterize-remaining-hooks
Jul 8, 2026
Merged

test(hooks): characterize the six remaining guard hooks#31
MANVENDRA-github merged 1 commit into
mainfrom
test/characterize-remaining-hooks

Conversation

@MANVENDRA-github

Copy link
Copy Markdown
Owner

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-dirs spent 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

Hook Blocks Deliberately allows
block-no-verify --no-verify, -n, --no-gpg-sign, -c commit.gpgsign=false echo --no-verify (not a git call)
block-force-push main/master, incl. --force-with-lease force-pushing a feature branch
guard-dangerous-bash /, /*, ~, $HOME, chmod -R 777 /, curl | sh, dd of=/dev/sd*, mkfs, fork bomb sudo rm -rf /var, rm -rf ./build, git reset --hard
protect-lockfile-edit 12 ecosystems' lockfiles package.json, source files
secret-scan-on-edit AWS key id, GitHub PAT, private-key header; reads new_string and content process.env.API_KEY, a placeholder in .env.example
block-secret-file-stage git add .env, the sweeping git add . / -A --dry-run, .env.example, a gitignored file

Two of those "allows" deserve saying out loud rather than treating as bugs:

  • guard-dangerous-bash does not block sudo rm -rf /var. Its header calls the list "intentionally narrow: unambiguously dangerous shapes, not commands that touch files." rm -rf /var is 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-push permits force-pushing a feature branch. That's the author's own history to rewrite.

block-secret-file-stage resolves 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 .env and server.key.

Fail-open is the contract

Every hook is asserted to exit 0 on empty stdin, malformed JSON, {}, a missing tool_input, and a null command/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) to exit(0) in block-force-push:

$ npm test
# pass 236
# fail 3        <- exactly the three force-push block cases

$ git checkout hooks/block-force-push.js && npm test
# pass 239
# fail 0

Verification

$ npm test
# tests 239     (was 143, was 98 before #30)
# suites 12
# pass 239
# fail 0

$ npm run lint    # PASS
$ npm run sync    # clean, no drift

All seven hooks now have coverage. Only README.md and the new test file change — no source or generated output touched.

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.
@MANVENDRA-github
MANVENDRA-github merged commit d231237 into main Jul 8, 2026
3 checks passed
@MANVENDRA-github
MANVENDRA-github deleted the test/characterize-remaining-hooks branch July 8, 2026 18:45
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