|
1 | 1 | # Git Permission Guard |
2 | 2 |
|
3 | | -A Claude Code plugin that provides centralized git and gh permission |
4 | | -management via PreToolUse hooks with detailed warning messages. |
5 | | - |
6 | | -## Overview |
7 | | - |
8 | | -This plugin replaces scattered permission rules with a single, intelligent |
9 | | -hook that: |
10 | | - |
11 | | -- Parses git and gh commands to understand their intent |
12 | | -- Applies allow/ask/deny logic based on command risk level |
13 | | -- Provides detailed explanations for blocked commands |
14 | | -- Handles `git -C <path>` by analyzing the actual subcommand |
| 3 | +PreToolUse hook that blocks dangerous git/gh commands with early exit |
| 4 | +optimization for non-git commands. |
15 | 5 |
|
16 | 6 | ## Installation |
17 | 7 |
|
18 | | -Add to your Claude Code settings: |
19 | | - |
20 | | -```json |
21 | | -{ |
22 | | - "plugins": [ |
23 | | - "/path/to/git-permission-guard" |
24 | | - ] |
25 | | -} |
26 | | -``` |
27 | | - |
28 | | -## Permission Categories |
29 | | - |
30 | | -### DENY (Never Allowed) |
31 | | - |
32 | | -Commands that bypass safety mechanisms are blocked with strong warnings: |
33 | | - |
34 | | -| Pattern | Reason | |
35 | | -| ------------------------------ | --------------------------- | |
36 | | -| `git commit --no-verify / -n` | Bypasses pre-commit hooks | |
37 | | -| `git merge --no-verify` | Bypasses merge hooks | |
38 | | -| `git cherry-pick --no-verify` | Bypasses commit hooks | |
39 | | -| `git rebase --no-verify` | Bypasses commit hooks | |
40 | | -| `git config core.hooksPath` | Changes hook directory | |
41 | | -| `git -c core.hooksPath=...` | Temporarily bypasses hooks | |
42 | | -| `pre-commit uninstall` | Removes pre-commit hooks | |
43 | | -| `rm .git/hooks/*` | Deletes hooks | |
44 | | -| `chmod -x .git/hooks/` | Disables hooks | |
45 | | - |
46 | | -### ASK (Require Confirmation) |
47 | | - |
48 | | -Potentially dangerous commands are blocked with detailed warnings: |
49 | | - |
50 | | -**Git Commands:** |
51 | | - |
52 | | -| Command | Risk | |
53 | | -| -------------------------- | -------------------------------------- | |
54 | | -| `git merge` | Can create merge commits or conflicts | |
55 | | -| `git reset` | Can lose uncommitted work permanently | |
56 | | -| `git restore` | Can discard local changes | |
57 | | -| `git rm` | Removes files from tree and index | |
58 | | -| `git cherry-pick` | Rewrites commit history | |
59 | | -| `git worktree remove` | Removes worktree directory | |
60 | | -| `git gc` | May remove unreferenced objects | |
61 | | -| `git prune` | Removes unreferenced objects | |
62 | | -| `git rebase` | Rewrites commit history | |
63 | | -| `git commit --amend` | Rewrites the last commit | |
64 | | -| `git push --force` | Overwrites remote history | |
65 | | -| `git push --force-w-lease` | Overwrites remote history | |
66 | | -| `git clean` | Removes untracked files permanently | |
67 | | - |
68 | | -**GitHub CLI Commands:** |
69 | | - |
70 | | -| Command | Risk | |
71 | | -| ------------------- | ------------------------------------------- | |
72 | | -| `gh repo delete` | Permanently deletes a repository | |
73 | | -| `gh issue close` | Closes issues (could be accidental) | |
74 | | -| `gh pr close` | Closes pull requests (could be accidental) | |
75 | | -| `gh pr merge` | Merges PR - ONLY when user requests | |
76 | | -| `gh release delete` | Deletes releases permanently | |
77 | | - |
78 | | -### ALLOW (Safe Commands) |
79 | | - |
80 | | -All other git/gh commands pass through silently: |
81 | | - |
82 | | -- `git status`, `git log`, `git diff`, `git add` |
83 | | -- `git commit` (without `--no-verify`) |
84 | | -- `git push` (without `--force`) |
85 | | -- `git pull`, `git fetch`, `git branch` |
86 | | -- `git checkout`, `git switch`, `git stash` |
87 | | -- `git tag`, `git remote`, `git show` |
88 | | -- `git blame`, `git bisect`, `git reflog` |
89 | | -- `git worktree add`, `git worktree list` |
90 | | -- `gh pr list`, `gh issue list`, `gh repo view` |
91 | | -- `gh auth`, `gh api`, etc. |
92 | | - |
93 | | -## Special Handling |
94 | | - |
95 | | -### `git -C <path>` Commands |
96 | | - |
97 | | -The hook intelligently handles `git -C <path>` by extracting the actual |
98 | | -subcommand: |
99 | | - |
100 | 8 | ```bash |
101 | | -git -C /some/path merge main # Triggers ASK (same as "git merge") |
102 | | -git -C /some/path status # Passes through (safe command) |
103 | | -git -C /path commit -n # Triggers DENY (bypasses hooks) |
| 9 | +claude plugins add jacobpevans-cc-plugins/git-permission-guard |
104 | 10 | ``` |
105 | 11 |
|
106 | | -### `git -c <key=value>` Commands |
| 12 | +## How It Works |
107 | 13 |
|
108 | | -Configuration options are parsed to find the actual subcommand: |
| 14 | +1. **Early exit** - Non-git/gh commands exit immediately (most Bash calls) |
| 15 | +2. **DENY** - Commands bypassing safety are always blocked |
| 16 | +3. **ASK** - Dangerous commands require user confirmation |
| 17 | +4. **ALLOW** - Safe commands pass through silently |
109 | 18 |
|
110 | | -```bash |
111 | | -git -c user.name="Test" commit -m "msg" # Passes through |
112 | | -git -c core.hooksPath=/dev/null commit # Triggers DENY |
113 | | -``` |
| 19 | +## Blocked Commands (DENY) |
114 | 20 |
|
115 | | -## Warning Message Examples |
| 21 | +| Pattern | Reason | |
| 22 | +| ----------------------------- | -------------------------- | |
| 23 | +| `git commit --no-verify / -n` | Bypasses pre-commit hooks | |
| 24 | +| `git merge --no-verify` | Bypasses merge hooks | |
| 25 | +| `git rebase --no-verify` | Bypasses commit hooks | |
| 26 | +| `git config core.hooksPath` | Changes hook directory | |
| 27 | +| `pre-commit uninstall` | Removes pre-commit hooks | |
| 28 | +| `rm .git/hooks/*` | Deletes git hooks | |
116 | 29 |
|
117 | | -### DENY Message |
| 30 | +## Confirmation Required (ASK) |
118 | 31 |
|
119 | | -```text |
120 | | -====================================================================== |
121 | | -BLOCKED: Prohibited Git Command |
122 | | -====================================================================== |
| 32 | +**Git:** |
123 | 33 |
|
124 | | -Command: git commit --no-verify |
| 34 | +- `merge` - Can create conflicts |
| 35 | +- `reset` - Can lose uncommitted work |
| 36 | +- `restore` - Can discard changes |
| 37 | +- `rm` - Removes from tree and index |
| 38 | +- `cherry-pick`, `rebase` - Rewrites history |
| 39 | +- `commit --amend` - Rewrites last commit |
| 40 | +- `push --force` - Overwrites remote |
| 41 | +- `clean` - Removes untracked files |
| 42 | +- `gc`, `prune` - May remove objects |
| 43 | +- `worktree remove` - Removes worktree |
125 | 44 |
|
126 | | -THIS COMMAND IS NEVER ALLOWED because it bypasses pre-commit hooks |
127 | | -that enforce code quality and security. |
| 45 | +**GitHub CLI:** |
128 | 46 |
|
129 | | -WHY THIS IS BLOCKED: |
130 | | - - Pre-commit hooks catch security vulnerabilities |
131 | | - - Hooks enforce consistent code formatting |
132 | | - - Bypassing hooks violates development standards |
| 47 | +- `repo delete` - Permanently deletes repo |
| 48 | +- `issue close` - Closes issues |
| 49 | +- `pr close` - Closes pull requests |
| 50 | +- `pr merge` - **ONLY when user EXPLICITLY requests** |
| 51 | +- `release delete` - Deletes releases |
133 | 52 |
|
134 | | -WHAT TO DO INSTEAD: |
135 | | - - Fix the issues that pre-commit hooks identify |
136 | | - - Run: pre-commit run --all-files |
| 53 | +## Special Handling |
137 | 54 |
|
138 | | -====================================================================== |
139 | | -``` |
| 55 | +`git -C <path>` and `git -c <key=value>` are parsed to extract the actual |
| 56 | +subcommand. `git -C /path merge` triggers the same rules as `git merge`. |
140 | 57 |
|
141 | | -### ASK Message |
| 58 | +## Structure |
142 | 59 |
|
143 | 60 | ```text |
144 | | -====================================================================== |
145 | | -CAUTION: Potentially Dangerous Git Command |
146 | | -====================================================================== |
147 | | -
|
148 | | -Command: git reset --hard HEAD~1 |
149 | | -
|
150 | | -RISK: Can permanently lose uncommitted work and rewrite local history |
151 | | -
|
152 | | -WHY THIS MATTERS: |
153 | | - - Uncommitted changes may be discarded forever |
154 | | - - Cannot be undone without reflog knowledge |
155 | | - - May cause confusion if you forget what was lost |
156 | | -
|
157 | | -SAFER ALTERNATIVES: |
158 | | - - git stash (saves changes temporarily) |
159 | | - - git reset --soft (keeps changes staged) |
160 | | - - git revert (preserves history) |
161 | | -
|
162 | | -To proceed, you must explicitly confirm this operation. |
163 | | -====================================================================== |
164 | | -``` |
165 | | - |
166 | | -## Configuration |
167 | | - |
168 | | -Configuration files are in the `config/` directory: |
169 | | - |
170 | | -- `deny.json` - Patterns that are never allowed |
171 | | -- `ask.json` - Patterns requiring confirmation |
172 | | - |
173 | | -### Adding Custom Rules |
174 | | - |
175 | | -**To deny a command:** |
176 | | - |
177 | | -```json |
178 | | -{ |
179 | | - "patterns": [ |
180 | | - { |
181 | | - "match": "your-regex-pattern", |
182 | | - "reason": "why this is blocked", |
183 | | - "alternative": "what to do instead" |
184 | | - } |
185 | | - ] |
186 | | -} |
187 | | -``` |
188 | | - |
189 | | -**To require confirmation:** |
190 | | - |
191 | | -```json |
192 | | -{ |
193 | | - "git": [ |
194 | | - { |
195 | | - "cmd": "subcommand", |
196 | | - "risk": "description of the risk", |
197 | | - "safer": ["alternative 1", "alternative 2"] |
198 | | - } |
199 | | - ] |
200 | | -} |
201 | | -``` |
202 | | - |
203 | | -## Exit Codes |
204 | | - |
205 | | -| Code | Meaning | Behavior | |
206 | | -| ---------------- | ------- | ------------------------------ | |
207 | | -| 0 (no output) | ALLOW | Command executes normally | |
208 | | -| 2 (with message) | BLOCK | Command is blocked with warning| |
209 | | - |
210 | | -## Testing |
211 | | - |
212 | | -Test the hook manually: |
213 | | - |
214 | | -```bash |
215 | | -# Should pass (exit 0, no output) |
216 | | -echo '{"tool_name":"Bash","tool_input":{"command":"git status"}}' \ |
217 | | - | python3 scripts/git-permission-guard.py |
218 | | - |
219 | | -# Should block (exit 2, deny message) |
220 | | -echo '{"tool_name":"Bash","tool_input":{"command":"git commit -n"}}' \ |
221 | | - | python3 scripts/git-permission-guard.py |
222 | | - |
223 | | -# Should block (exit 2, ask message) |
224 | | -echo '{"tool_name":"Bash","tool_input":{"command":"git reset --hard"}}' \ |
225 | | - | python3 scripts/git-permission-guard.py |
226 | | - |
227 | | -# Should pass (non-git command) |
228 | | -echo '{"tool_name":"Bash","tool_input":{"command":"ls -la"}}' \ |
229 | | - | python3 scripts/git-permission-guard.py |
| 61 | +git-permission-guard/ |
| 62 | +├── .claude-plugin/plugin.json |
| 63 | +├── hooks/hooks.json |
| 64 | +├── scripts/git-permission-guard.py |
| 65 | +└── README.md |
230 | 66 | ``` |
231 | 67 |
|
232 | | -## License |
| 68 | +## Sources |
233 | 69 |
|
234 | | -MIT |
| 70 | +- [Claude Code Hooks Reference](https://docs.anthropic.com/en/docs/claude-code/hooks) |
0 commit comments