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.
Files changed (75) hide show
  1. package/COOKBOOK.md +70 -0
  2. package/README.md +43 -4
  3. package/TROUBLESHOOTING.md +30 -0
  4. package/examples/api-rate-limit-tracker.sh +51 -0
  5. package/examples/auto-answer-question.sh +67 -0
  6. package/examples/auto-approve-readonly-tools.sh +10 -0
  7. package/examples/aws-production-guard.sh +40 -0
  8. package/examples/banned-command-guard.sh +48 -0
  9. package/examples/bash-heuristic-approver.sh +59 -0
  10. package/examples/block-database-wipe.sh +1 -1
  11. package/examples/classifier-fallback-allow.sh +70 -0
  12. package/examples/commit-message-check.sh +8 -1
  13. package/examples/commit-message-quality.sh +35 -0
  14. package/examples/credential-exfil-guard.sh +12 -0
  15. package/examples/cwd-reminder.sh +37 -0
  16. package/examples/dependency-install-guard.sh +84 -0
  17. package/examples/deploy-guard.sh +1 -1
  18. package/examples/detect-mixed-indentation.sh +33 -0
  19. package/examples/disk-space-check.sh +42 -0
  20. package/examples/docker-dangerous-guard.sh +47 -0
  21. package/examples/dockerfile-lint.sh +58 -0
  22. package/examples/edit-always-allow.sh +53 -0
  23. package/examples/env-file-gitignore-check.sh +39 -0
  24. package/examples/env-source-guard.sh +1 -1
  25. package/examples/git-stash-before-danger.sh +58 -0
  26. package/examples/github-actions-guard.sh +49 -0
  27. package/examples/gitignore-auto-add.sh +30 -0
  28. package/examples/go-vet-after-edit.sh +33 -0
  29. package/examples/hook-tamper-guard.sh +67 -0
  30. package/examples/kubernetes-guard.sh +2 -1
  31. package/examples/large-file-write-guard.sh +40 -0
  32. package/examples/main-branch-warn.sh +40 -0
  33. package/examples/max-edit-size-guard.sh +9 -15
  34. package/examples/mcp-server-guard.sh +70 -0
  35. package/examples/multiline-command-approver.sh +89 -0
  36. package/examples/no-base64-exfil.sh +27 -0
  37. package/examples/no-debug-commit.sh +60 -0
  38. package/examples/no-exposed-port-in-dockerfile.sh +32 -0
  39. package/examples/no-fixme-ship.sh +41 -0
  40. package/examples/no-hardcoded-ip.sh +26 -0
  41. package/examples/no-http-in-code.sh +19 -0
  42. package/examples/no-push-without-tests.sh +33 -0
  43. package/examples/no-self-signed-cert.sh +19 -0
  44. package/examples/no-star-import-python.sh +28 -0
  45. package/examples/no-wget-piped-bash.sh +22 -0
  46. package/examples/node-version-check.sh +40 -0
  47. package/examples/npm-publish-guard.sh +5 -2
  48. package/examples/output-token-env-check.sh +44 -0
  49. package/examples/package-lock-frozen.sh +25 -0
  50. package/examples/pip-venv-required.sh +40 -0
  51. package/examples/port-conflict-check.sh +62 -0
  52. package/examples/prefer-builtin-tools.sh +33 -0
  53. package/examples/python-import-check.sh +52 -0
  54. package/examples/python-ruff-on-edit.sh +51 -0
  55. package/examples/quoted-flag-approver.sh +51 -0
  56. package/examples/react-key-warn.sh +32 -0
  57. package/examples/rm-safety-net.sh +9 -0
  58. package/examples/rust-clippy-after-edit.sh +37 -0
  59. package/examples/session-quota-tracker.sh +44 -0
  60. package/examples/session-start-safety-check.sh +60 -0
  61. package/examples/session-summary-stop.sh +49 -0
  62. package/examples/session-time-limit.sh +34 -0
  63. package/examples/temp-file-cleanup.sh +41 -0
  64. package/examples/test-before-push.sh +8 -1
  65. package/examples/test-coverage-reminder.sh +49 -0
  66. package/examples/test-exit-code-verify.sh +60 -0
  67. package/examples/tool-file-logger.sh +46 -0
  68. package/examples/typescript-lint-on-edit.sh +61 -0
  69. package/examples/typescript-strict-check.sh +35 -0
  70. package/examples/uncommitted-changes-stop.sh +16 -0
  71. package/examples/uncommitted-discard-guard.sh +72 -0
  72. package/examples/worktree-unmerged-guard.sh +13 -3
  73. package/examples/yaml-syntax-check.sh +50 -0
  74. package/index.mjs +3 -0
  75. 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
  [![npm downloads](https://img.shields.io/npm/dw/cc-safe-setup)](https://www.npmjs.com/package/cc-safe-setup)
5
5
  [![tests](https://github.com/yurukusa/cc-safe-setup/actions/workflows/test.yml/badge.svg)](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 335 examples |
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 — 16 hooks + 6 templates + 3 exclusive tools + install.sh. Pay What You Want.
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://docs.anthropic.com/en/docs/claude-code/hooks) — Claude Code hooks documentation
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
@@ -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*;?\s*$'; then
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": [{ "type": "command", "command": "~/.claude/hooks/commit-message-check.sh" }]
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