Skip to content

Remove the Bash(git *) blanket allow and add destructive-git deny rules - #208

Merged
ikuwow merged 5 commits into
mainfrom
feature/explicit-git-allowlist-187
Jul 27, 2026
Merged

Remove the Bash(git *) blanket allow and add destructive-git deny rules#208
ikuwow merged 5 commits into
mainfrom
feature/explicit-git-allowlist-187

Conversation

@ikuwow

@ikuwow ikuwow commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Issue: #187 (the issue recommended an enumerated allowlist; this PR lands a smaller shape than that recommendation — see Background)

Why

Bash(git *) auto-approved every git subcommand at the rule layer, including the ACE vectors the issue lists (git config core.pager / core.editor / core.sshCommand / core.hooksPath writes, git apply/am against untrusted patches) and any unknown or future subcommand. Rule-layer allow matches skip every downstream safety check, so the blanket entry has to go regardless of permission mode.

What

  • Remove Bash(git *) from permissions.allow. Nothing replaces it.
  • Add 7 deny entries closing the destructive-push and single-file-discard gaps from the issue: git push --delete / git push * --delete / git push --mirror / git push * --mirror / the colon-prefix branch-delete refspec (git push * :*), plus git checkout -- * / git checkout * -- *.
  • The ask list is untouched. Main's existing git claw-backs (git rebase *, git reset *, git clean *, git branch -D *, git config *, etc.) stay and keep forcing prompts.

Background: why no allowlist

This settings file runs with permissions.defaultMode: "auto". Per the official docs (code.claude.com/docs permissions / permission-modes pages, checked 2026-07-27):

  • Read-only Bash commands (git status, git log, ...) are auto-approved in every mode, so enumerating them in allow is redundant
  • git add (working-directory file writes) and git push to the working repository are auto-approved under auto mode (push since v2.1.211)
  • Rule precedence is deny -> ask -> allow independent of mode, and ask rules force a prompt even in auto mode

So under auto mode, deny + ask lists are the two levers that matter; an enumerated read-only allowlist adds 60+ lines of maintenance surface with no behavioral effect. An earlier revision of this branch carried that allowlist; it was dropped in the final commit.

Note: this PR makes destructive git subcommands (reset, clean, rebase, ...) flow to the ask claw-backs or the auto-mode classifier instead of being rule-approved by the blanket allow. Occasional new prompts for unusual git writes are the intended behavior change. If a specific safe command turns out to prompt annoyingly often, add a narrow allow entry for it rather than restoring the blanket.

Verification

  • jq . claude/settings.json validates
  • git diff main -- claude/settings.json is exactly 1 removed allow line + 7 added deny lines
  • No duplicate entries in allow/ask/deny (jq ... | sort | uniq -d empty)
  • The deny rules already bite: during the rework, git checkout origin/main -- claude/settings.json was blocked by this branch's own Bash(git checkout * -- *) deny entry (worked around via git show), confirming the pattern matches as intended
  • Live behavior after merge + fresh session: git config core.pager <x> and other config writes prompt; git push origin :branch / git checkout -- file are denied; routine status/log/diff/add/push flow without new prompts

ikuwow and others added 2 commits June 7, 2026 15:02
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 <noreply@anthropic.com>
…allowlist-187

Resolve claude/settings.json conflicts:
- deny: union of this branch push --delete/--mirror/colon-refspec and
  checkout -- denies with main force-with-lease denies
- ask: apply this branch removal of the git claw-back entries (they
  fall through to default ask), keep main gh api patterns, and do not
  restore gh issue edit which main removed in #311

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RGD2NDthkx859WNGeDKKBN
@ikuwow ikuwow mentioned this pull request Jul 20, 2026
3 tasks
ikuwow added a commit that referenced this pull request Jul 20, 2026
Move Bash(git merge *) from the ask list to the allow list. The ask
rule forced a prompt on every merge even though the blanket
Bash(git *) allow matched (ask rules take precedence over allow), which
blocked subagents from resolving PR branch conflicts: a background
permission prompt gives the user no context about which branch is
being merged into.

The explicit allow entry is redundant with Bash(git *) today, but
becomes load-bearing once #208 replaces the blanket with an explicit
allowlist: without it, merges would fall through to the auto-mode
classifier (occasional transient-error denials) or a manual-mode
prompt.

git merge is local-only and recoverable: committed state stays
reachable via the merge commit parents, uncommitted work aborts the
merge instead of being overwritten, and nothing touches the remote
until a separate push.


Claude-Session: https://claude.ai/code/session_01RGD2NDthkx859WNGeDKKBN

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ikuwow and others added 3 commits July 20, 2026 23:59
…allowlist-187

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0156RhYyZmXJn2YdA1rHZztf
…allowlist-187

# Conflicts:
#	claude/settings.json
defaultMode "auto" auto-approves read-only Bash commands and honors
ask rules with a guaranteed prompt, so the enumerated read-only git
allowlist added earlier in this branch is redundant. Drop the
allowlist, keep the core of #187: remove the Bash(git *) blanket
allow (closes the git-config ACE vectors at the rule layer) and add
the deny entries for destructive push refspecs and single-file
checkout discards. Main's ask claw-backs stay untouched — under auto
mode they are the guarantee of a prompt for destructive git commands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Vi9VXxJvaXAHgYRoyrJsQ
@ikuwow ikuwow changed the title Replace Bash(git *) blanket allow with an explicit git allowlist Remove the Bash(git *) blanket allow and add destructive-git deny rules Jul 27, 2026
@ikuwow
ikuwow marked this pull request as ready for review July 27, 2026 07:06
@ikuwow
ikuwow merged commit 590b518 into main Jul 27, 2026
6 checks passed
@ikuwow
ikuwow deleted the feature/explicit-git-allowlist-187 branch July 27, 2026 07:06
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