Harden the commit guard, test the hooks, and gate on CI - #1
Merged
Merged
Conversation
…fy in guard-commit A chained 'git commit -m ok && git push -f' was allowed: the force-push check was skipped whenever a commit appeared anywhere in the same Bash call. Quoted text is now stripped and the command is split on ; & | so every segment is classified and checked independently. Also blocked: --no-verify on commit/push, the -n short form, core.hooksPath overrides, and +refspec force-pushes. Adds tests/hooks/ with one stdin-payload case per rule and per past regression (39 guard cases, 4 format cases), all bash 3.2 + jq.
shellcheck, settings JSON, agent name invariant, scaffolder-template diff, and both hook test suites. Same commands as the local gate.
…tion blocker, name untrusted principal sources code-reviewer now takes the Phase 1 plan as an input and traces plan → diff as well as diff → task, reporting plan coverage. The 🔴 'test that doesn't test what it claims' gets a criterion: presence-only or was-called-only assertions, tests that only exercise the fake, and assertions loosened in the diff; qa pushes back on the same. security-reviewer and code-reviewer state that the acting principal comes from the verified session or token, never from the request body, a tool-call argument, or model output, and that tool arguments and model output are taint sources.
Wait for CI and classify red per §4.7; reply to every top-level review comment with a pushed SHA, a deferred issue, or a reasoned disagreement; re-check after every push; done means green and replied, never merged.
…th the tested gate §19.2/§19.3/§19.6 describe the hook tests, CI, and the copies-in-sync diff instead of 'nothing to unit-test' and 'no CI yet'. §4 and §9 pointers cover the plan reverse-trace and the PR-open-to-merge loop. README and STRUCTURE list --no-verify among what the hook blocks, add tests/hooks and the CI job, and state the hook boundary rule. The two scaffolder CLAUDE.md.tmpl files had drifted from templates/CLAUDE.project.md (the format hook matches *.md, not *.tmpl); they are byte-identical again and the gate diffs them. Reconciliation gains a cited-paths-resolve and duplicates-in-sync step.
Force-push: -f inside a flag cluster (-uf, -fu) and --mirror are now caught. Quote stripping is multi-line aware and drops escaped quotes first, so heredoc and multi-line commit messages that mention a flag are allowed again, and an escaped quote cannot flip parity. Backslash-newline continuations are joined before classification. Shell wrappers (sh -c, bash -c, eval) around a git commit/push are refused rather than guessed at. core.hooksPath and GIT_CONFIG_PARAMETERS are checked across the whole call. An unparseable payload that mentions git commit/push now fails closed. The secrets scan covers git add && git commit in one call (working tree plus untracked files), reads only added lines so scrubbing a leak stays committable, and runs git diff with --no-ext-diff and core.fsmonitor=false so repo-local config cannot execute a program. Tests: committer cases no longer run in a subshell, the suite asserts every defined case was counted, the private-key header is built at runtime, and 34 cases were added (73 total).
…eviewer; scope the cited-paths grep
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.
Summary
The commit guard had a real hole and no tests. A chained
git commit -m ok && git push -f origin xwas allowed because the force-push check was skipped whenever a commit appeared anywhere in the same Bash call. This PR fixes that, adds the behavioral test suite that should have caught it, wires the quality gate into CI, and folds in five smaller improvements to the reviewer agents and the PR workflow docs. Everything stays bash 3.2 + jq + git; no new dependencies.;,&,|and newlines, and each segment is classified as commit / push / add independently.-finside a flag cluster (-uf),--mirror,+refspec. Bare--force-if-includesis also caught; it is a no-op without--force-with-lease, so nothing legitimate is lost.--no-verify,git commit -n(alone or in a cluster like-anm),core.hooksPathoverrides via-c,git config, orGIT_CONFIG_PARAMETERS. Section 14 already forbade this; the guard now enforces it.sh -c "…",bash -c "…", andeval "…"around a git commit/push exit 2 with a reason; the unwrapped form is always available.jqis missing.git add … && git commitin one call (working tree plus untracked files, at hook time nothing is staged yet), reads only added lines so scrubbing a leaked key stays committable, and runsgit diff --no-ext-diffwithcore.fsmonitor=falseso repo-local config cannot make the guard execute a program..github/workflows/gate.ymlruns the §19.3 gate on every PR and on push tomain, read-only token, checkout action pinned to a commit.code-reviewertakes the Phase 1 plan as an input and traces plan → diff as well as diff → task, reporting plan coverage. The blocking "test that doesn't test what it claims" gets a criterion (presence-only or was-called-only assertions; assertions loosened in the diff), andqapushes back on the same.security-reviewerandcode-reviewerstate that the acting principal comes from the verified session or token, never from the request body, a tool-call argument, or model output, and that tool arguments and model output are taint sources.--no-verifyamong what the hook blocks and state the hook boundary rule. The two scaffolderCLAUDE.md.tmplcopies had drifted fromtemplates/CLAUDE.project.md(the format hook matches*.md, not*.tmpl); they are byte-identical again and the gate diffs them.docs/reconciliation.mdgains a cited-paths-resolve and duplicates-in-sync step.Deferred / out of scope
p=push; git $p -f), aliases defined in an earlier call, flags fed throughxargs, and quoting tricks like--no-veri"fy"are documented as accepted limits in the hook header. The guard is a backstop behind the permission deny rules and plan mode, not a sandbox.Test plan
shellcheck hooks/*.sh tests/hooks/*.sh,jq empty settings.json settings2.json, agent-name grep, bothdiff -qtemplate checks,bash tests/hooks/test-guard-commit.sh(73 passed),bash tests/hooks/test-format.sh(4 passed)/bin/bash3.2.57 on macOS; no bash-4-only constructs-uf,-fu,+refspec, continuation lines, heredoc messages, escaped quotes,sh -c,eval, non-JSON payload, missingjq, repo-localdiff.external(verified not executed), 200 KB command (under 1 s)prettier --checkclean on every changed markdown file