cc-safe-setup 29.6.0 → 29.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +70 -0
- package/README.md +43 -4
- package/TROUBLESHOOTING.md +30 -0
- package/examples/api-rate-limit-tracker.sh +51 -0
- package/examples/auto-answer-question.sh +67 -0
- package/examples/auto-approve-readonly-tools.sh +10 -0
- package/examples/aws-production-guard.sh +40 -0
- package/examples/banned-command-guard.sh +48 -0
- package/examples/bash-heuristic-approver.sh +59 -0
- package/examples/block-database-wipe.sh +1 -1
- package/examples/classifier-fallback-allow.sh +70 -0
- package/examples/commit-message-check.sh +8 -1
- package/examples/commit-message-quality.sh +35 -0
- package/examples/credential-exfil-guard.sh +12 -0
- package/examples/cwd-reminder.sh +37 -0
- package/examples/dependency-install-guard.sh +84 -0
- package/examples/deploy-guard.sh +1 -1
- package/examples/detect-mixed-indentation.sh +33 -0
- package/examples/disk-space-check.sh +42 -0
- package/examples/docker-dangerous-guard.sh +47 -0
- package/examples/dockerfile-lint.sh +58 -0
- package/examples/edit-always-allow.sh +53 -0
- package/examples/env-file-gitignore-check.sh +39 -0
- package/examples/env-source-guard.sh +1 -1
- package/examples/git-stash-before-danger.sh +58 -0
- package/examples/github-actions-guard.sh +49 -0
- package/examples/gitignore-auto-add.sh +30 -0
- package/examples/go-vet-after-edit.sh +33 -0
- package/examples/hook-tamper-guard.sh +67 -0
- package/examples/kubernetes-guard.sh +2 -1
- package/examples/large-file-write-guard.sh +40 -0
- package/examples/main-branch-warn.sh +40 -0
- package/examples/max-edit-size-guard.sh +9 -15
- package/examples/mcp-server-guard.sh +70 -0
- package/examples/multiline-command-approver.sh +89 -0
- package/examples/no-base64-exfil.sh +27 -0
- package/examples/no-debug-commit.sh +60 -0
- package/examples/no-exposed-port-in-dockerfile.sh +32 -0
- package/examples/no-fixme-ship.sh +41 -0
- package/examples/no-hardcoded-ip.sh +26 -0
- package/examples/no-http-in-code.sh +19 -0
- package/examples/no-push-without-tests.sh +33 -0
- package/examples/no-self-signed-cert.sh +19 -0
- package/examples/no-star-import-python.sh +28 -0
- package/examples/no-wget-piped-bash.sh +22 -0
- package/examples/node-version-check.sh +40 -0
- package/examples/npm-publish-guard.sh +5 -2
- package/examples/output-token-env-check.sh +44 -0
- package/examples/package-lock-frozen.sh +25 -0
- package/examples/pip-venv-required.sh +40 -0
- package/examples/port-conflict-check.sh +62 -0
- package/examples/prefer-builtin-tools.sh +33 -0
- package/examples/python-import-check.sh +52 -0
- package/examples/python-ruff-on-edit.sh +51 -0
- package/examples/quoted-flag-approver.sh +51 -0
- package/examples/react-key-warn.sh +32 -0
- package/examples/rm-safety-net.sh +9 -0
- package/examples/rust-clippy-after-edit.sh +37 -0
- package/examples/session-quota-tracker.sh +44 -0
- package/examples/session-start-safety-check.sh +60 -0
- package/examples/session-summary-stop.sh +49 -0
- package/examples/session-time-limit.sh +34 -0
- package/examples/temp-file-cleanup.sh +41 -0
- package/examples/test-before-push.sh +8 -1
- package/examples/test-coverage-reminder.sh +49 -0
- package/examples/test-exit-code-verify.sh +60 -0
- package/examples/tool-file-logger.sh +46 -0
- package/examples/typescript-lint-on-edit.sh +61 -0
- package/examples/typescript-strict-check.sh +35 -0
- package/examples/uncommitted-changes-stop.sh +16 -0
- package/examples/uncommitted-discard-guard.sh +72 -0
- package/examples/worktree-unmerged-guard.sh +13 -3
- package/examples/yaml-syntax-check.sh +50 -0
- package/index.mjs +3 -0
- package/package.json +2 -2
package/COOKBOOK.md
CHANGED
|
@@ -145,6 +145,76 @@ npx cc-safe-setup --install-example allow-git-hooks-dir
|
|
|
145
145
|
|
|
146
146
|
Or manually: create a PermissionRequest hook that outputs `permissionDecision: "allow"`. See [Troubleshooting](TROUBLESHOOTING.md#pretooluse-allow-doesnt-bypass-protected-directory-prompts) for details.
|
|
147
147
|
|
|
148
|
+
## Credential Protection
|
|
149
|
+
|
|
150
|
+
Block credential hunting commands (env scanning, file searches for tokens):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npx cc-safe-setup --install-example credential-exfil-guard
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Blocks: `env | grep -i token`, `find / -name *.pem`, `cat ~/.ssh/id_rsa`, `cat ~/.aws/credentials`.
|
|
157
|
+
|
|
158
|
+
## Extra rm Protection
|
|
159
|
+
|
|
160
|
+
Add a second layer of rm protection beyond destructive-guard:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npx cc-safe-setup --install-example rm-safety-net
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Blocks rm -rf on any non-safe path (only allows node_modules, dist, build, /tmp, __pycache__).
|
|
167
|
+
|
|
168
|
+
## Auto Mode False Positive Fix
|
|
169
|
+
|
|
170
|
+
Stop the safety classifier from blocking read-only commands:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npx cc-safe-setup --install-example auto-mode-safe-commands
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Auto-approves: cat, grep, git status, ls, find, jq, curl GET, echo.
|
|
177
|
+
|
|
178
|
+
## Compound Command Auto-Approve
|
|
179
|
+
|
|
180
|
+
Stop permission prompts for `cd /path && git log`:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npx cc-safe-setup --install-example compound-command-allow
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Splits compound commands and checks each component. Approves when all are safe.
|
|
187
|
+
|
|
188
|
+
## Secret Leak Prevention (Write/Edit)
|
|
189
|
+
|
|
190
|
+
Block secrets from being written into source files:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npx cc-safe-setup --install-example write-secret-guard
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Detects AWS, GitHub, OpenAI, Anthropic, Slack, Stripe, Google keys + PEM + database URLs. Allows .env.example and test files.
|
|
197
|
+
|
|
198
|
+
## Permission Audit Log
|
|
199
|
+
|
|
200
|
+
Log every tool call for debugging permission rules:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npx cc-safe-setup --install-example permission-audit-log
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Writes JSONL to `~/.claude/tool-usage.jsonl`. Analyze with `cat ~/.claude/tool-usage.jsonl | jq -s 'group_by(.tool) | map({tool: .[0].tool, count: length})'`.
|
|
207
|
+
|
|
208
|
+
## Classifier Fallback
|
|
209
|
+
|
|
210
|
+
Auto-approve read-only commands when Auto Mode's classifier is unavailable:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npx cc-safe-setup --install-example classifier-fallback-allow
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
PermissionRequest hook that approves cat, ls, grep, git read-only when the classifier can't respond.
|
|
217
|
+
|
|
148
218
|
## Further Reading
|
|
149
219
|
|
|
150
220
|
- [Getting Started](https://yurukusa.github.io/cc-safe-setup/getting-started.html)
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/cc-safe-setup)
|
|
5
5
|
[](https://github.com/yurukusa/cc-safe-setup/actions/workflows/test.yml)
|
|
6
6
|
|
|
7
|
-
**One command to make Claude Code safe for autonomous operation.** [日本語](docs/README.ja.md)
|
|
7
|
+
**One command to make Claude Code safe for autonomous operation.** 1,000+ installs/day · [日本語](docs/README.ja.md)
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx cc-safe-setup
|
|
@@ -66,6 +66,34 @@ Claude Code ships with no safety hooks by default. This tool fixes that.
|
|
|
66
66
|
|
|
67
67
|
Each hook exists because a real incident happened without it.
|
|
68
68
|
|
|
69
|
+
### v2.1.85: `if` Field Support
|
|
70
|
+
|
|
71
|
+
Hooks now support an `if` field for conditional execution. The hook process only spawns when the command matches the pattern — `ls` won't trigger a git-only hook.
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"type": "command",
|
|
76
|
+
"if": "Bash(git push *)",
|
|
77
|
+
"command": "~/.claude/hooks/test-before-push.sh"
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
All example hooks include `if` field documentation in their headers.
|
|
82
|
+
|
|
83
|
+
## PermissionRequest Hooks (NEW)
|
|
84
|
+
|
|
85
|
+
Override Claude Code's built-in confirmation prompts. These run **after** the built-in safety checks, so they can auto-approve prompts that `permissions.allow` cannot suppress.
|
|
86
|
+
|
|
87
|
+
| Hook | What It Solves | Issue |
|
|
88
|
+
|------|---------------|-------|
|
|
89
|
+
| `quoted-flag-approver` | "Quoted characters in flag names" prompt on `git commit -m "msg"` | [#27957](https://github.com/anthropics/claude-code/issues/27957) |
|
|
90
|
+
| `bash-heuristic-approver` | Safety heuristic prompts for `$()`, backticks, ANSI-C quoting | [#30435](https://github.com/anthropics/claude-code/issues/30435) |
|
|
91
|
+
| `edit-always-allow` | Edit prompts in `.claude/skills/` despite `bypassPermissions` | [#36192](https://github.com/anthropics/claude-code/issues/36192) |
|
|
92
|
+
| `allow-git-hooks-dir` | Edit prompts in `.git/hooks/` for pre-commit/pre-push setup | |
|
|
93
|
+
| `allow-protected-dirs` | All protected directory prompts (CI/Docker environments) | [#36168](https://github.com/anthropics/claude-code/issues/36168) |
|
|
94
|
+
|
|
95
|
+
Install any of these: `npx cc-safe-setup --install-example <name>`
|
|
96
|
+
|
|
69
97
|
## All 49 Commands
|
|
70
98
|
|
|
71
99
|
| Command | What It Does |
|
|
@@ -89,7 +117,7 @@ Each hook exists because a real incident happened without it.
|
|
|
89
117
|
| `--scan [--apply]` | Tech stack detection |
|
|
90
118
|
| `--export / --import` | Team config sharing |
|
|
91
119
|
| `--verify` | Test each hook |
|
|
92
|
-
| `--install-example <name>` | Install from
|
|
120
|
+
| `--install-example <name>` | Install from 369 examples |
|
|
93
121
|
| `--examples [filter]` | Browse examples by keyword |
|
|
94
122
|
| `--full` | All-in-one setup |
|
|
95
123
|
| `--status` | Check installed hooks |
|
|
@@ -147,6 +175,16 @@ Each hook exists because a real incident happened without it.
|
|
|
147
175
|
| Maximum protection mode | `npx cc-safe-setup --safe-mode` |
|
|
148
176
|
| Migrate from Cursor/Windsurf | [Migration Guide](https://yurukusa.github.io/cc-safe-setup/migration-guide.html) |
|
|
149
177
|
|
|
178
|
+
## Common Pain Points (from GitHub Issues)
|
|
179
|
+
|
|
180
|
+
| Problem | Issue | Fix |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| Claude uses `cat`/`grep`/`sed` instead of built-in Read/Edit/Grep | [#19649](https://github.com/anthropics/claude-code/issues/19649) (48👍) | `npx cc-safe-setup --install-example prefer-builtin-tools` |
|
|
183
|
+
| `cd /path && cmd` bypasses permission allowlist | [#28240](https://github.com/anthropics/claude-code/issues/28240) (88👍) | `npx cc-safe-setup --install-example compound-command-approver` |
|
|
184
|
+
| Multiline commands skip pattern matching | [#11932](https://github.com/anthropics/claude-code/issues/11932) (47👍) | Use hooks instead of allowlist patterns for complex commands |
|
|
185
|
+
| No notification when Claude asks a question | [#13024](https://github.com/anthropics/claude-code/issues/13024) (52👍) | `npx cc-safe-setup --install-example notify-waiting` |
|
|
186
|
+
| `allow` overrides `ask` in permissions | [#6527](https://github.com/anthropics/claude-code/issues/6527) (17👍) | Use hooks to block dangerous commands instead of `ask` rules |
|
|
187
|
+
|
|
150
188
|
## How It Works
|
|
151
189
|
|
|
152
190
|
1. Writes hook scripts to `~/.claude/hooks/`
|
|
@@ -215,7 +253,7 @@ npx cc-health-check
|
|
|
215
253
|
|
|
216
254
|
cc-safe-setup gives you 8 essential hooks. Want to know what else your setup needs?
|
|
217
255
|
|
|
218
|
-
Run `npx cc-health-check` (free, 20 checks) to see your current score. If it's below 80, the **[Claude Code Ops Kit](https://yurukusa.github.io/cc-ops-kit-landing/?utm_source=github&utm_medium=readme&utm_campaign=safe-setup)** fills the gaps —
|
|
256
|
+
Run `npx cc-health-check` (free, 20 checks) to see your current score. If it's below 80, the **[Claude Code Ops Kit](https://yurukusa.github.io/cc-ops-kit-landing/?utm_source=github&utm_medium=readme&utm_campaign=safe-setup)** fills the gaps — 6 hooks + 5 templates + 9 scripts + install.sh. Pay What You Want ($0+).
|
|
219
257
|
|
|
220
258
|
Or browse the free hooks: [claude-code-hooks](https://github.com/yurukusa/claude-code-hooks)
|
|
221
259
|
|
|
@@ -362,9 +400,10 @@ See [Issue #1](https://github.com/yurukusa/cc-safe-setup/issues/1) for details.
|
|
|
362
400
|
## Learn More
|
|
363
401
|
|
|
364
402
|
- [Cookbook](COOKBOOK.md) — 26 practical recipes (block, approve, protect, monitor, diagnose)
|
|
365
|
-
- [Official Hooks Reference](https://
|
|
403
|
+
- [Official Hooks Reference](https://code.claude.com/docs/en/hooks) — Claude Code hooks documentation
|
|
366
404
|
- [Hooks Cookbook](https://github.com/yurukusa/claude-code-hooks/blob/main/COOKBOOK.md) — 25 recipes from real GitHub Issues ([interactive version](https://yurukusa.github.io/claude-code-hooks/))
|
|
367
405
|
- [Japanese guide (Qiita)](https://qiita.com/yurukusa/items/a9714b33f5d974e8f1e8) — この記事の日本語解説
|
|
406
|
+
- [v2.1.85 `if` field guide (Qiita)](https://qiita.com/yurukusa/items/7079866e9dc239fcdd57) — Reduce hook overhead with conditional execution
|
|
368
407
|
- [Hook Test Runner](https://github.com/yurukusa/cc-hook-test) — `npx cc-hook-test <hook.sh>` to auto-test any hook
|
|
369
408
|
- [Hook Registry](https://github.com/yurukusa/cc-hook-registry) — `npx cc-hook-registry search database` ([browse online](https://yurukusa.github.io/cc-hook-registry/))
|
|
370
409
|
- [Hooks Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/cheatsheet.html) — printable A4 quick reference
|
package/TROUBLESHOOTING.md
CHANGED
|
@@ -261,6 +261,36 @@ npx cc-safe-setup --status # See which hooks are active
|
|
|
261
261
|
|
|
262
262
|
This should be fixed in a future Claude Code release.
|
|
263
263
|
|
|
264
|
+
## "write-secret-guard blocks normal code"
|
|
265
|
+
|
|
266
|
+
The write-secret-guard hook may false-positive on strings that look like API keys (20+ alphanumeric characters after specific prefixes). Fix:
|
|
267
|
+
|
|
268
|
+
1. If the blocked file is a test file, rename it to include `test` in the path
|
|
269
|
+
2. If it's a `.env.example`, the hook should already allow it — check the filename pattern
|
|
270
|
+
3. For specific false positives, add an allowlist pattern to the hook
|
|
271
|
+
|
|
272
|
+
## "credential-exfil-guard blocks my grep"
|
|
273
|
+
|
|
274
|
+
The hook blocks `grep` commands that search for secret-related keywords. If you need to search for `token` or `key` in code:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# This is blocked:
|
|
278
|
+
env | grep -i token
|
|
279
|
+
|
|
280
|
+
# This is allowed (searching code, not environment):
|
|
281
|
+
grep "token" src/auth.js
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The hook only blocks `env/printenv/set` piped to grep with secret keywords, not general file searches.
|
|
285
|
+
|
|
286
|
+
## "compound-command-allow doesn't approve my command"
|
|
287
|
+
|
|
288
|
+
The hook has a strict whitelist. If a command isn't on the list, it passes through to the normal permission system. Common misses:
|
|
289
|
+
|
|
290
|
+
- `docker` commands (not whitelisted — install `auto-approve-docker` instead)
|
|
291
|
+
- `pip install` (not whitelisted — install `pip-venv-guard` instead)
|
|
292
|
+
- Custom scripts (unknown to the whitelist)
|
|
293
|
+
|
|
264
294
|
## Still Stuck?
|
|
265
295
|
|
|
266
296
|
1. Wrap the hook with debug wrapper: `npx cc-safe-setup --install-example hook-debug-wrapper`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# api-rate-limit-tracker.sh — Track API call frequency and warn on burst
|
|
3
|
+
#
|
|
4
|
+
# Prevents: Rate limit errors from rapid API calls.
|
|
5
|
+
# Claude sometimes runs curl/fetch in tight loops.
|
|
6
|
+
#
|
|
7
|
+
# Tracks: API calls per minute via a log file.
|
|
8
|
+
# Warns at: 10 calls/min, blocks at 30 calls/min.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse
|
|
11
|
+
# MATCHER: "Bash"
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# {
|
|
15
|
+
# "hooks": {
|
|
16
|
+
# "PreToolUse": [{
|
|
17
|
+
# "matcher": "Bash",
|
|
18
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/api-rate-limit-tracker.sh" }]
|
|
19
|
+
# }]
|
|
20
|
+
# }
|
|
21
|
+
# }
|
|
22
|
+
|
|
23
|
+
INPUT=$(cat)
|
|
24
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
25
|
+
[ -z "$COMMAND" ] && exit 0
|
|
26
|
+
|
|
27
|
+
# Only track API-calling commands
|
|
28
|
+
echo "$COMMAND" | grep -qiE '(curl|wget|http|fetch)\s' || exit 0
|
|
29
|
+
|
|
30
|
+
LOG="/tmp/cc-api-rate-$$"
|
|
31
|
+
NOW=$(date +%s)
|
|
32
|
+
|
|
33
|
+
# Append timestamp
|
|
34
|
+
echo "$NOW" >> "$LOG"
|
|
35
|
+
|
|
36
|
+
# Count calls in the last 60 seconds
|
|
37
|
+
CUTOFF=$((NOW - 60))
|
|
38
|
+
RECENT=$(awk -v cutoff="$CUTOFF" '$1 >= cutoff' "$LOG" 2>/dev/null | wc -l)
|
|
39
|
+
|
|
40
|
+
if [ "$RECENT" -ge 30 ]; then
|
|
41
|
+
echo "BLOCKED: $RECENT API calls in the last minute. Rate limit risk." >&2
|
|
42
|
+
echo " Add delays between calls or batch requests." >&2
|
|
43
|
+
exit 2
|
|
44
|
+
elif [ "$RECENT" -ge 10 ]; then
|
|
45
|
+
echo "WARNING: $RECENT API calls in the last minute. Slow down." >&2
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Cleanup old entries
|
|
49
|
+
awk -v cutoff="$CUTOFF" '$1 >= cutoff' "$LOG" > "${LOG}.tmp" 2>/dev/null && mv "${LOG}.tmp" "$LOG"
|
|
50
|
+
|
|
51
|
+
exit 0
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# auto-answer-question.sh — Auto-answer AskUserQuestion for headless/autonomous mode
|
|
3
|
+
#
|
|
4
|
+
# NOTE: updatedInput schema (question/answer vs questions/answers array)
|
|
5
|
+
# may vary. Verify with your Claude Code version before production use.
|
|
6
|
+
#
|
|
7
|
+
# TRIGGER: PreToolUse
|
|
8
|
+
# MATCHER: AskUserQuestion
|
|
9
|
+
#
|
|
10
|
+
# v2.1.85+: PreToolUse hooks can satisfy AskUserQuestion by returning
|
|
11
|
+
# updatedInput alongside permissionDecision: "allow".
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# {
|
|
15
|
+
# "hooks": {
|
|
16
|
+
# "PreToolUse": [{
|
|
17
|
+
# "matcher": "AskUserQuestion",
|
|
18
|
+
# "hooks": [{
|
|
19
|
+
# "type": "command",
|
|
20
|
+
# "command": "~/.claude/hooks/auto-answer-question.sh"
|
|
21
|
+
# }]
|
|
22
|
+
# }]
|
|
23
|
+
# }
|
|
24
|
+
# }
|
|
25
|
+
#
|
|
26
|
+
# Dangerous operations → answer NO
|
|
27
|
+
# Safe operations (test, build, lint) → answer YES
|
|
28
|
+
# Unknown questions → pass through to human
|
|
29
|
+
|
|
30
|
+
INPUT=$(cat)
|
|
31
|
+
QUESTION=$(echo "$INPUT" | jq -r '.tool_input.question // empty' 2>/dev/null)
|
|
32
|
+
[ -z "$QUESTION" ] && exit 0
|
|
33
|
+
|
|
34
|
+
# Log all auto-answered questions for audit
|
|
35
|
+
LOG_DIR="${HOME}/.claude/audit"
|
|
36
|
+
mkdir -p "$LOG_DIR"
|
|
37
|
+
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') Q: $QUESTION" >> "$LOG_DIR/auto-answers.log"
|
|
38
|
+
|
|
39
|
+
# Dangerous operations → always NO
|
|
40
|
+
if echo "$QUESTION" | grep -qiE 'delete|削除|drop|truncate|destroy|remove.*all|wipe|reset.*hard|force.*push|rm -rf'; then
|
|
41
|
+
jq -n --arg q "$QUESTION" '{
|
|
42
|
+
hookSpecificOutput: {
|
|
43
|
+
hookEventName: "PreToolUse",
|
|
44
|
+
permissionDecision: "allow",
|
|
45
|
+
updatedInput: { question: $q, answer: "No. This operation is too risky for unattended mode." }
|
|
46
|
+
}
|
|
47
|
+
}'
|
|
48
|
+
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') A: NO (dangerous)" >> "$LOG_DIR/auto-answers.log"
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Safe operations → always YES
|
|
53
|
+
if echo "$QUESTION" | grep -qiE 'test|テスト|build|ビルド|lint|format|check|確認|verify'; then
|
|
54
|
+
jq -n --arg q "$QUESTION" '{
|
|
55
|
+
hookSpecificOutput: {
|
|
56
|
+
hookEventName: "PreToolUse",
|
|
57
|
+
permissionDecision: "allow",
|
|
58
|
+
updatedInput: { question: $q, answer: "Yes, proceed." }
|
|
59
|
+
}
|
|
60
|
+
}'
|
|
61
|
+
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') A: YES (safe op)" >> "$LOG_DIR/auto-answers.log"
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Unknown → pass through to human
|
|
66
|
+
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') A: PASS (unknown)" >> "$LOG_DIR/auto-answers.log"
|
|
67
|
+
exit 0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
INPUT=$(cat)
|
|
2
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
3
|
+
case "$TOOL" in
|
|
4
|
+
Read|Glob|Grep)
|
|
5
|
+
jq -n --arg tool "$TOOL" \
|
|
6
|
+
'{"hookSpecificOutput":{"hookEventName":"PermissionRequest","permissionDecision":"allow","permissionDecisionReason":($tool + " is read-only, auto-approved")}}'
|
|
7
|
+
exit 0
|
|
8
|
+
;;
|
|
9
|
+
esac
|
|
10
|
+
exit 0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# aws-production-guard.sh — Block dangerous AWS CLI operations
|
|
3
|
+
#
|
|
4
|
+
# Prevents: Accidental deletion of production resources.
|
|
5
|
+
# Blocks: aws s3 rm --recursive, aws ec2 terminate-instances,
|
|
6
|
+
# aws rds delete-db-instance, aws cloudformation delete-stack
|
|
7
|
+
#
|
|
8
|
+
# TRIGGER: PreToolUse
|
|
9
|
+
# MATCHER: "Bash"
|
|
10
|
+
|
|
11
|
+
INPUT=$(cat)
|
|
12
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
13
|
+
[ -z "$COMMAND" ] && exit 0
|
|
14
|
+
|
|
15
|
+
# Only check AWS CLI commands
|
|
16
|
+
echo "$COMMAND" | grep -qE '^\s*aws\s' || exit 0
|
|
17
|
+
|
|
18
|
+
# Block destructive operations
|
|
19
|
+
BLOCKED_PATTERNS=(
|
|
20
|
+
"s3.*rm.*--recursive"
|
|
21
|
+
"s3.*rb\s"
|
|
22
|
+
"ec2.*terminate-instances"
|
|
23
|
+
"rds.*delete-db"
|
|
24
|
+
"cloudformation.*delete-stack"
|
|
25
|
+
"lambda.*delete-function"
|
|
26
|
+
"dynamodb.*delete-table"
|
|
27
|
+
"iam.*delete-user"
|
|
28
|
+
"iam.*delete-role"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
for pattern in "${BLOCKED_PATTERNS[@]}"; do
|
|
32
|
+
if echo "$COMMAND" | grep -qiE "aws\s+$pattern"; then
|
|
33
|
+
echo "BLOCKED: Destructive AWS operation detected." >&2
|
|
34
|
+
echo " Pattern: $pattern" >&2
|
|
35
|
+
echo " Use the AWS Console for destructive operations." >&2
|
|
36
|
+
exit 2
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
exit 0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# banned-command-guard.sh — Block commands that are explicitly banned
|
|
3
|
+
#
|
|
4
|
+
# Solves: Claude using sed/awk/perl one-liners to edit files instead of
|
|
5
|
+
# the built-in Edit tool, even when CLAUDE.md says "never use sed."
|
|
6
|
+
# Real incident: #36413 — sed from wrong CWD emptied a key file,
|
|
7
|
+
# then git checkout -- discarded 400 lines of uncommitted work.
|
|
8
|
+
#
|
|
9
|
+
# Why this matters:
|
|
10
|
+
# CLAUDE.md bans are advisory. Claude can ignore them under context pressure.
|
|
11
|
+
# This hook enforces the ban at the process level.
|
|
12
|
+
#
|
|
13
|
+
# Default banned commands (configurable via CC_BANNED_COMMANDS):
|
|
14
|
+
# sed -i (in-place file editing — use Edit tool instead)
|
|
15
|
+
# awk -i inplace (same reason)
|
|
16
|
+
# perl -pi -e (same reason)
|
|
17
|
+
#
|
|
18
|
+
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
19
|
+
#
|
|
20
|
+
# Configuration:
|
|
21
|
+
# CC_BANNED_COMMANDS — colon-separated list of regex patterns to block
|
|
22
|
+
# Default: "sed -i:awk -i inplace:perl -pi"
|
|
23
|
+
|
|
24
|
+
INPUT=$(cat)
|
|
25
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
26
|
+
|
|
27
|
+
[ -z "$COMMAND" ] && exit 0
|
|
28
|
+
|
|
29
|
+
# Configurable banned patterns (colon-separated)
|
|
30
|
+
BANNED="${CC_BANNED_COMMANDS:-sed\s+-i:awk\s+-i\s+inplace:perl\s+-pi}"
|
|
31
|
+
|
|
32
|
+
# Check each banned pattern
|
|
33
|
+
IFS=':' read -ra PATTERNS <<< "$BANNED"
|
|
34
|
+
for pattern in "${PATTERNS[@]}"; do
|
|
35
|
+
if echo "$COMMAND" | grep -qE "$pattern"; then
|
|
36
|
+
echo "BLOCKED: Command matches banned pattern." >&2
|
|
37
|
+
echo "" >&2
|
|
38
|
+
echo "Command: $COMMAND" >&2
|
|
39
|
+
echo "Pattern: $pattern" >&2
|
|
40
|
+
echo "" >&2
|
|
41
|
+
echo "Use the built-in Edit tool instead of shell text processors." >&2
|
|
42
|
+
echo "Edit tool preserves file encoding, handles unicode correctly," >&2
|
|
43
|
+
echo "and shows diffs for review." >&2
|
|
44
|
+
exit 2
|
|
45
|
+
fi
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
exit 0
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# bash-heuristic-approver.sh — Auto-approve bash safety heuristic prompts
|
|
3
|
+
#
|
|
4
|
+
# Solves: Safety heuristic prompts cannot be suppressed (#30435, 30 reactions)
|
|
5
|
+
# Claude Code fires prompts for common patterns like:
|
|
6
|
+
# - $() command substitution
|
|
7
|
+
# - Backtick substitution
|
|
8
|
+
# - Newlines in commands (for loops, multi-step scripts)
|
|
9
|
+
# - Quote characters in comments
|
|
10
|
+
# - ANSI-C quoting
|
|
11
|
+
# These cannot be bypassed with permissions.allow or acceptEdits.
|
|
12
|
+
#
|
|
13
|
+
# This PermissionRequest hook detects heuristic-triggered prompts and
|
|
14
|
+
# auto-approves them when the base command is in a safe list.
|
|
15
|
+
#
|
|
16
|
+
# TRIGGER: PermissionRequest
|
|
17
|
+
# MATCHER: ""
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# {
|
|
21
|
+
# "hooks": {
|
|
22
|
+
# "PermissionRequest": [{
|
|
23
|
+
# "matcher": "",
|
|
24
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/bash-heuristic-approver.sh" }]
|
|
25
|
+
# }]
|
|
26
|
+
# }
|
|
27
|
+
# }
|
|
28
|
+
|
|
29
|
+
INPUT=$(cat)
|
|
30
|
+
|
|
31
|
+
# Detect safety heuristic prompts by their characteristic messages
|
|
32
|
+
MESSAGE=$(echo "$INPUT" | jq -r '.message // empty' 2>/dev/null)
|
|
33
|
+
[ -z "$MESSAGE" ] && exit 0
|
|
34
|
+
|
|
35
|
+
# Match known heuristic warning patterns
|
|
36
|
+
HEURISTIC_PATTERNS="command substitution|backtick|can desync quote|potential bypass|can hide characters|quoted characters|newline|ANSI.C quot"
|
|
37
|
+
|
|
38
|
+
if ! echo "$MESSAGE" | grep -qiE "$HEURISTIC_PATTERNS"; then
|
|
39
|
+
# Not a heuristic prompt — pass through
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Extract the command
|
|
44
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
45
|
+
[ -z "$COMMAND" ] && exit 0
|
|
46
|
+
|
|
47
|
+
# Safe base commands — only auto-approve for known-safe tools
|
|
48
|
+
SAFE_COMMANDS="git|npm|npx|bun|yarn|pnpm|docker|make|cargo|go|pip|python3|node|tsc|eslint|prettier|jest|vitest|pytest|gh|curl|wget|rsync|tar|zip|unzip|find|grep|sed|awk|cat|echo|ls|mkdir|cp|mv|chmod|wc|sort|head|tail|jq|python3"
|
|
49
|
+
|
|
50
|
+
# Extract first meaningful command (skip env vars, cd, whitespace)
|
|
51
|
+
BASE_CMD=$(echo "$COMMAND" | tr '\n' ' ' | sed 's/^[[:space:]]*//' | sed 's/^[A-Z_]*=[^ ]* //' | sed 's/^cd [^&;]* *[&;]* *//' | awk '{print $1}' | sed 's|.*/||')
|
|
52
|
+
|
|
53
|
+
if echo "$BASE_CMD" | grep -qE "^($SAFE_COMMANDS)$"; then
|
|
54
|
+
echo '{"permissionDecision":"allow"}'
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Unknown base command — let the prompt through for safety
|
|
59
|
+
exit 0
|
|
@@ -57,7 +57,7 @@ if echo "$COMMAND" | grep -qiE 'rake\s+db:(drop|reset)|rails\s+db:(drop|reset)';
|
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
59
|
# Raw SQL destructive commands
|
|
60
|
-
if echo "$COMMAND" | grep -qiE 'DROP\s+(DATABASE|TABLE|SCHEMA)|TRUNCATE\s+TABLE|DELETE\s+FROM\s+\w+\s
|
|
60
|
+
if echo "$COMMAND" | grep -qiE 'DROP\s+(DATABASE|TABLE|SCHEMA)|TRUNCATE\s+TABLE|DELETE\s+FROM\s+\w+\s*(;|\s*$|WHERE\s+(1\s*=\s*1|true))'; then
|
|
61
61
|
echo "BLOCKED: Destructive SQL command" >&2
|
|
62
62
|
exit 2
|
|
63
63
|
fi
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# classifier-fallback-allow.sh — Allow read-only commands when Auto Mode classifier is unavailable
|
|
3
|
+
#
|
|
4
|
+
# Solves: Auto Mode's safety classifier (Sonnet) sometimes goes down.
|
|
5
|
+
# When it does, ALL commands get blocked — even cat, ls, grep.
|
|
6
|
+
# (#39259, #38618, #38537)
|
|
7
|
+
#
|
|
8
|
+
# How it works: PermissionRequest hook that approves read-only commands
|
|
9
|
+
# regardless of classifier status. Only fires on PermissionRequest
|
|
10
|
+
# (the permission prompt), not on normal PreToolUse.
|
|
11
|
+
#
|
|
12
|
+
# Usage: Add to settings.json as a PermissionRequest hook
|
|
13
|
+
#
|
|
14
|
+
# {
|
|
15
|
+
# "hooks": {
|
|
16
|
+
# "PermissionRequest": [{
|
|
17
|
+
# "matcher": "Bash",
|
|
18
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/classifier-fallback-allow.sh" }]
|
|
19
|
+
# }]
|
|
20
|
+
# }
|
|
21
|
+
# }
|
|
22
|
+
|
|
23
|
+
INPUT=$(cat)
|
|
24
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
25
|
+
|
|
26
|
+
[ -z "$COMMAND" ] && exit 0
|
|
27
|
+
|
|
28
|
+
# Extract the base command (first word, ignoring leading whitespace)
|
|
29
|
+
BASE=$(echo "$COMMAND" | awk '{print $1}')
|
|
30
|
+
|
|
31
|
+
# Read-only commands that are always safe
|
|
32
|
+
case "$BASE" in
|
|
33
|
+
# File inspection
|
|
34
|
+
cat|head|tail|less|more|wc|file|stat|du|df|ls|tree|find|which|whereis|type|realpath|readlink|basename|dirname)
|
|
35
|
+
# find with -delete is NOT read-only
|
|
36
|
+
if echo "$COMMAND" | grep -qE '\s-delete'; then
|
|
37
|
+
exit 0 # Don't approve — let normal flow handle it
|
|
38
|
+
fi
|
|
39
|
+
jq -n '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","permissionDecision":"allow","permissionDecisionReason":"Read-only command (classifier fallback)"}}'
|
|
40
|
+
exit 0
|
|
41
|
+
;;
|
|
42
|
+
# Text search
|
|
43
|
+
grep|rg|ag|ack)
|
|
44
|
+
jq -n '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","permissionDecision":"allow","permissionDecisionReason":"Text search (classifier fallback)"}}'
|
|
45
|
+
exit 0
|
|
46
|
+
;;
|
|
47
|
+
# Git read-only
|
|
48
|
+
git)
|
|
49
|
+
SUBCMD=$(echo "$COMMAND" | awk '{print $2}')
|
|
50
|
+
case "$SUBCMD" in
|
|
51
|
+
status|log|diff|show|branch|tag|remote|describe|rev-parse|blame|shortlog|ls-files|ls-tree)
|
|
52
|
+
jq -n '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","permissionDecision":"allow","permissionDecisionReason":"Git read-only (classifier fallback)"}}'
|
|
53
|
+
exit 0
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
;;
|
|
57
|
+
# Shell builtins
|
|
58
|
+
echo|printf|true|false|pwd|env|printenv|date|uname|hostname|whoami|id)
|
|
59
|
+
jq -n '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","permissionDecision":"allow","permissionDecisionReason":"Shell builtin (classifier fallback)"}}'
|
|
60
|
+
exit 0
|
|
61
|
+
;;
|
|
62
|
+
# JSON/YAML
|
|
63
|
+
jq|yq)
|
|
64
|
+
jq -n '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","permissionDecision":"allow","permissionDecisionReason":"Data processing (classifier fallback)"}}'
|
|
65
|
+
exit 0
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
|
|
69
|
+
# Not a known read-only command — don't interfere
|
|
70
|
+
exit 0
|
|
@@ -13,10 +13,17 @@
|
|
|
13
13
|
# "hooks": {
|
|
14
14
|
# "PostToolUse": [{
|
|
15
15
|
# "matcher": "Bash",
|
|
16
|
-
# "hooks": [{
|
|
16
|
+
# "hooks": [{
|
|
17
|
+
# "type": "command",
|
|
18
|
+
# "if": "Bash(git commit *)",
|
|
19
|
+
# "command": "~/.claude/hooks/commit-message-check.sh"
|
|
20
|
+
# }]
|
|
17
21
|
# }]
|
|
18
22
|
# }
|
|
19
23
|
# }
|
|
24
|
+
#
|
|
25
|
+
# The "if" field (v2.1.85+) skips this hook for non-commit commands.
|
|
26
|
+
# Without "if", the hook still works — it checks internally and exits early.
|
|
20
27
|
|
|
21
28
|
INPUT=$(cat)
|
|
22
29
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# commit-message-quality.sh — Warn about low-quality commit messages
|
|
3
|
+
#
|
|
4
|
+
# Prevents: "fix", "update", "wip", "asdf" commit messages.
|
|
5
|
+
# Claude sometimes generates vague messages.
|
|
6
|
+
#
|
|
7
|
+
# Checks: minimum length, conventional commit format suggestion
|
|
8
|
+
#
|
|
9
|
+
# TRIGGER: PreToolUse
|
|
10
|
+
# MATCHER: "Bash"
|
|
11
|
+
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
14
|
+
[ -z "$COMMAND" ] && exit 0
|
|
15
|
+
|
|
16
|
+
# Only check git commit
|
|
17
|
+
echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
|
|
18
|
+
|
|
19
|
+
# Extract commit message
|
|
20
|
+
MSG=$(echo "$COMMAND" | grep -oP '(-m\s+["\x27])(.+?)(["\x27])' | sed "s/^-m\s*[\"']//" | sed "s/[\"']$//")
|
|
21
|
+
[ -z "$MSG" ] && exit 0
|
|
22
|
+
|
|
23
|
+
# Check message quality
|
|
24
|
+
LEN=${#MSG}
|
|
25
|
+
|
|
26
|
+
if [ "$LEN" -lt 10 ]; then
|
|
27
|
+
echo "WARNING: Commit message too short ($LEN chars). Be more descriptive." >&2
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Check for vague messages
|
|
31
|
+
if echo "$MSG" | grep -qiE '^(fix|update|change|wip|temp|test|asdf|todo|misc|stuff|things|more)$'; then
|
|
32
|
+
echo "WARNING: Vague commit message '$MSG'. Describe WHAT changed and WHY." >&2
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
exit 0
|
|
@@ -70,4 +70,16 @@ if echo "$COMMAND" | grep -qE '^\s*(env|printenv|set)\s*$'; then
|
|
|
70
70
|
exit 0
|
|
71
71
|
fi
|
|
72
72
|
|
|
73
|
+
# Pattern 8: curl/wget posting credential files
|
|
74
|
+
if echo "$COMMAND" | grep -qiP 'curl\s.*-d\s+@[^\s]*(\.env|\.pem|\.key|credentials|\.ssh/id_)|wget\s.*--post-file[= ][^\s]*(\.env|\.pem|\.key|credentials|\.ssh/id_)'; then
|
|
75
|
+
echo "BLOCKED: Credential file exfiltration via HTTP upload" >&2
|
|
76
|
+
exit 2
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Pattern 9: Piping credential files to curl/wget
|
|
80
|
+
if echo "$COMMAND" | grep -qiP 'cat\s+[^\s]*(\.env|\.pem|\.key|credentials|\.ssh/id_)\S*\s*\|.*curl|cat\s+[^\s]*(\.env|\.pem|\.key|credentials|\.ssh/id_)\S*\s*\|.*wget'; then
|
|
81
|
+
echo "BLOCKED: Credential file piped to HTTP client" >&2
|
|
82
|
+
exit 2
|
|
83
|
+
fi
|
|
84
|
+
|
|
73
85
|
exit 0
|