Skip to content

Replace Bash(git *) blanket allow with an explicit git allowlist#208

Draft
ikuwow wants to merge 1 commit into
mainfrom
feature/explicit-git-allowlist-187
Draft

Replace Bash(git *) blanket allow with an explicit git allowlist#208
ikuwow wants to merge 1 commit into
mainfrom
feature/explicit-git-allowlist-187

Conversation

@ikuwow

@ikuwow ikuwow commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Closes #187. Picks the recommended approach from the issue (read-only allowlist + routine writes guarded by deny).

Why

Bash(git *) auto-approved every git subcommand, including the four ACE vectors the issue lists (git config core.pager/core.editor/core.sshCommand/core.hooksPath, plus inline git -c ...), git apply/am against untrusted patches, and any unknown or future subcommand. It also blocked the legitimate auto-approve of git config --get because the git config * ask rule shadowed it. Both problems come from the same root: the blanket allow.

What

  • Bash(git *) is removed. In its place: the enumerated read-only allowlist from the issue (status / log / show / diff / blame, branch reads by flag, tag / remote reads by flag, rev-parse family, ls-files / ls-tree / ls-remote / for-each-ref / cat-file, shortlog / grep / cherry / count-objects / fsck, verify-commit/tag, whatchanged, reflog, git config --get* family, stash list / show, worktree list).
  • Routine writes the daily workflow needs are also in allow: git add *, git push / push *, git pull / pull *, git fetch*, git checkout -b *, git switch *, git restore --staged *, git stash / stash push / pop / apply, git worktree add *. Their destructive variants stay denied (or are added to deny — see below).
  • Deny additions to close the gaps identified in §"deny gaps" of the issue: git push --delete, git push --mirror, git push * :* (the colon-prefix branch-delete refspec), git checkout -- * (single-file discard).
  • Ask claw-backs that became redundant once git * was gone are removed: git merge *, git rebase *, git commit --amend* (4 entries), git reset *, git clean *, git stash drop *, git stash clear *, git branch -D * (2 entries), git config *, git worktree prune * / move * / lock * / unlock *. They all fall through to the default ask via the same path that catches every other non-allowlisted git subcommand.

Notes

  • git commit is hook-approved by claude/hooks/approve_git_gh_commands.py independent of these rules, so removing git commit --amend from ask does not regress git commit itself; an --amend will land in the default ask path along with the rest of the unsafe subset.
  • git push * :* over-matches: it also blocks git push remote master:main (a legitimate refspec push). That is acceptable for the personal-dotfiles use case — colon-style pushes are rare here, and the alternative is to leave the gap open.
  • Bash(git config --get*) now auto-approves while git config user.email <value> (write) falls into ask — the concrete symptom in the issue is resolved.
  • Globals reach: claude/settings.json is symlinked into ~/.claude/, so this affects every project. The allowlist was sized to cover routine cross-project usage; if a workflow ever needs an additional subcommand auto-approved, add it explicitly rather than restoring the blanket allow.

Verification

  • jq . claude/settings.json validates.
  • Static check of the new allow rules vs. the ACE-vector list in the issue: git config core.pager, core.editor, core.sshCommand, core.hooksPath writes do not match any allow pattern (allowlist is limited to git config --get* family). git -c core.pager=<cmd> status does not match Bash(git status*) because the prefix requires literal git status, not git -c .... Both vectors are closed.
  • Deny-before-allow ordering: git push * --force * and the new git push * :* precede their allow counterparts in evaluation order per the Claude Code permission precedence (denyaskallow).
  • Live behaviour requires the merged settings to be active in a fresh session, which is not possible from inside the current session. Defer the following until after merge + restart:
    • git status / git log / git diff / git add <file> / git push / git pull / git checkout -b foo / git switch foo run without new prompts.
    • git config --get user.email auto-approves.
    • git config user.email <x> prompts.
    • git push --delete origin foo, git push origin :foo, git checkout -- file.txt are denied.
    • git push --force still denied.
    • git commit still auto-approved by the hook.

Replaces the catch-all "Bash(git *)" with an enumerated allowlist of
safe read-only subcommands plus the routine writes the daily workflow
needs (add, push, pull, fetch, checkout -b, switch, restore --staged,
stash push/pop/apply, worktree add). Everything else falls through to
the default ask (config writes, merge, rebase, reset, cherry-pick,
revert, apply, am, rm, mv, branch -D, tag -d, worktree prune/move,
inline -c key=val, and any unknown subcommand) — including the four
arbitrary-code-execution vectors the issue flagged: git config
core.pager / core.editor / core.sshCommand / core.hooksPath.

Closes the deny gaps identified in #187: git push --delete, git push
--mirror, the colon-prefix branch-delete refspec, and single-file
git checkout -- <file>.

Prunes the git ask claw-backs that became redundant once "Bash(git *)"
was gone (merge, rebase, commit --amend, reset, clean, stash drop/
clear, branch -D, config, worktree prune/move/lock/unlock). They now
default to ask via the same fallthrough that catches every other
non-allowed subcommand.

Closes #187.

Co-authored-by: Claude Opus 4.6 <[email protected]>
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.

Replace over-broad Bash(git *) allow with an explicit git allowlist

1 participant