Skip to content

fix(global-settings): deny an unauthorized git push in every form, before any prompt (v2.7.2) - #806

Merged
WilcoLouwerse merged 1 commit into
mainfrom
fix/global-settings-git-c-push-bypass
Oct 1, 2026
Merged

WilcoLouwerse merged 1 commit into
mainfrom
fix/global-settings-git-c-push-bypass

Conversation

@WilcoLouwerse

Copy link
Copy Markdown
Contributor

Problem

block-write-commands.sh should hard-deny every git push unless the last typed message has an authorization phrase. Two gaps let a push through without the phrase:

  1. Ordering. The push check ran after the git -C, gh, curl and docker ask guards. An ask exits the hook, so a push chained after one of them was never checked. The user saw a prompt about the first part, and approving it also ran the push:
    • git -C repo commit -m x && git push
    • git -C repo add . && git -C repo push origin main
    • gh pr create … && git push, gh api -X POST … ; git push
    • curl -X POST … && git push, docker compose up -d; git push
  2. git's global options. \bgit\s+push\b does not match when an option sits between git and push. These passed with no deny and no prompt from the hook:
    • git -c k=v push
    • git --no-pager push, git -P push
    • git --work-tree /x push
    • git -C "/a b" push (this one was asked, not denied)

Fix

  • The push guard now sits before every ask, next to the production guard (same reasoning as v2.6.0).

  • It matches git, then any of git's global options, then push. The global options it knows:

    • -C/-c <value>;
    • --git-dir/--work-tree/--namespace/--config-env/--super-prefix/--attr-source <value>;
    • --flag[=value];
    • -p/-P.

    Values may be quoted. Other long options take no separate value, so git --no-pager log --author push is not mistaken for a push.

  • With the phrase, the rest of the command still reaches its own guard. For example, git -C r commit … && git push still asks about the commit.

  • data_free_cmd now also strips -m/--body/… values on git <global options> commit|tag|notes lines. Otherwise moving the guard up would hard-deny git -C r commit -m "explain git push". That command is now asked about, as before.

  • VERSION 2.7.1 → 2.7.2. The README has a new section, and the guard table in docs/claude/global-claude-settings.md is updated.

Tests

tests/test-block-write-commands.sh gains 26 fixtures and 9 transcript-backed checks:

  • every form above is denied without the phrase;
  • with the phrase, a chained push still asks about the other part;
  • with the phrase, git -c …/--no-pager pushes are allowed;
  • -C-commit messages that mention push, a directory named push, and --author push are not denied.
Suite Result
test-block-write-commands.sh 7630 / 7630
same suite against the hook on main 7612 / 7630 — the 18 failures are exactly the new push fixtures
test-block-config-tool-writes.sh 80 / 80
test-block-polling.sh 29 / 29
test-check-settings-version.sh 81 / 81

ShellCheck did not run locally (not installed); CI runs it.

Not in this PR

Several other hard denies still sit after ask guards, so the same chaining reaches them only after a prompt:

  • date -s
  • --legacy-peer-deps
  • ln to ~/.claude/
  • the script-body scanner
  • the WSL boundary

Fixing that generally (an ask that records its reason and lets later hard denies run, emitting the ask at the end) is a separate change.

🤖 Generated with Claude Code

…fore any prompt (v2.7.2)

The push check ran after the git -C, gh, curl and docker ask guards. An
ask exits the hook, so `git -C repo commit -m x && git push` (or the same
after `gh pr create`, `curl -X POST`, `docker compose up`) prompted once
for the first part and, on approval, ran the push without the phrase.
git's global options before the subcommand were not recognised at all:
`git -c k=v push`, `git --no-pager push` and `git --work-tree <path> push`
passed with no deny and no prompt.

The push guard now sits before every ask, next to the production guard,
and matches `git` followed by any of its global options (-C/-c <value>,
--git-dir/--work-tree/--namespace/... <value>, --flag[=value], -p/-P)
and then `push`. With the phrase, the rest of the command is still
asked about. data_free_cmd strips -m/--body values on
`git <global options> commit|tag|notes` lines too, so a message that
mentions git push after `git -C <path> commit` is not denied as a push.

Tests: 26 fixtures for the forms above plus 9 transcript-backed checks
(phrase given → still asks for the chained part; no phrase → denied).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@WilcoLouwerse
WilcoLouwerse merged commit 24397f0 into main Oct 1, 2026
48 checks passed
@WilcoLouwerse
WilcoLouwerse deleted the fix/global-settings-git-c-push-bypass branch October 1, 2026 16:08
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