cc-safe-setup 29.6.40 → 29.7.0

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 (79) hide show
  1. package/.claude-plugin/marketplace.json +66 -0
  2. package/.claude-plugin/plugin.json +11 -0
  3. package/README.md +123 -12
  4. package/SETTINGS_REFERENCE.md +2 -0
  5. package/SKILL.md +47 -0
  6. package/examples/README.md +11 -1
  7. package/examples/auto-approve-compound-git.sh +3 -0
  8. package/examples/auto-compact-context-monitor.sh +35 -0
  9. package/examples/auto-mode-safety-enforcer.sh +57 -0
  10. package/examples/background-task-guard.sh +57 -0
  11. package/examples/broad-find-guard.sh +62 -0
  12. package/examples/cache-creation-spike-detector.sh +32 -0
  13. package/examples/case-insensitive-path-guard.sh +96 -0
  14. package/examples/cjk-punctuation-guard.sh +44 -0
  15. package/examples/clipboard-secret-guard.sh +29 -0
  16. package/examples/context-size-alert.sh +38 -0
  17. package/examples/context-usage-drift-alert.sh +33 -0
  18. package/examples/dangerous-pip-flag-guard.sh +51 -0
  19. package/examples/deny-bypass-detector.sh +143 -0
  20. package/examples/dotenv-read-guard.sh +48 -0
  21. package/examples/dotfile-protection-guard.sh +60 -0
  22. package/examples/effort-tracking-logger.sh +30 -0
  23. package/examples/financial-operation-guard.sh +47 -0
  24. package/examples/full-rewrite-detector.sh +63 -0
  25. package/examples/home-critical-bash-guard.sh +56 -0
  26. package/examples/idle-session-cost-alert.sh +36 -0
  27. package/examples/model-version-alert.sh +18 -0
  28. package/examples/model-version-change-alert.sh +31 -0
  29. package/examples/move-delete-sequence-guard.sh +92 -0
  30. package/examples/pii-upload-guard.sh +72 -0
  31. package/examples/pr-duplicate-guard.sh +14 -0
  32. package/examples/production-port-kill-guard.sh +60 -0
  33. package/examples/quota-reset-cycle-monitor.sh +30 -0
  34. package/examples/repo-visibility-guard.sh +33 -0
  35. package/examples/sandbox-relative-path-audit.sh +51 -0
  36. package/examples/session-agent-cost-limiter.sh +43 -0
  37. package/examples/session-cost-alert.sh +62 -0
  38. package/examples/session-memory-watchdog.sh +9 -0
  39. package/examples/settings-integrity-monitor.sh +55 -0
  40. package/examples/settings-json-model-guard.sh +89 -0
  41. package/examples/shell-config-truncation-guard.sh +97 -0
  42. package/examples/shell-wrapper-guard.sh +4 -4
  43. package/examples/subagent-spawn-rate-monitor.sh +34 -0
  44. package/examples/subcommand-chain-guard.sh +44 -0
  45. package/examples/system-dir-protection-guard.sh +100 -0
  46. package/examples/thinking-display-enforcer.sh +25 -0
  47. package/examples/tool-retry-budget-guard.sh +59 -0
  48. package/examples/worktree-branch-pollution-detector.sh +35 -0
  49. package/examples/worktree-create-log.sh +6 -0
  50. package/examples/worktree-hook-linker.sh +72 -0
  51. package/examples/worktree-remove-uncommitted-guard.sh +20 -0
  52. package/hooks/hooks.json +60 -0
  53. package/index.mjs +92 -6
  54. package/memory/market-anthropic-japan-strategy-2026-04-13.md +4 -0
  55. package/package.json +2 -2
  56. package/plugins/credential-guard/.claude-plugin/plugin.json +58 -0
  57. package/plugins/git-protection/.claude-plugin/plugin.json +58 -0
  58. package/plugins/safety-essentials/.claude-plugin/plugin.json +58 -0
  59. package/plugins/token-guard/.claude-plugin/plugin.json +51 -0
  60. package/skills/safety-setup/SKILL.md +47 -0
  61. package/tests/dotenv-read-guard.test.sh +65 -0
  62. package/tests/test-auto-mode-safety-enforcer.sh +55 -0
  63. package/tests/test-case-insensitive-path-guard.sh +78 -0
  64. package/tests/test-context-usage-drift-alert.sh +52 -0
  65. package/tests/test-dangerous-pip-flag-guard.sh +56 -0
  66. package/tests/test-dotfile-protection-guard.sh +68 -0
  67. package/tests/test-effort-tracking-logger.sh +55 -0
  68. package/tests/test-financial-operation-guard.sh +59 -0
  69. package/tests/test-home-critical-bash-guard.sh +59 -0
  70. package/tests/test-model-version-change-alert.sh +55 -0
  71. package/tests/test-move-delete-sequence-guard.sh +63 -0
  72. package/tests/test-pr-duplicate-guard.sh +29 -0
  73. package/tests/test-quota-reset-cycle-monitor.sh +52 -0
  74. package/tests/test-shell-config-truncation-guard.sh +104 -0
  75. package/tests/test-subagent-spawn-rate-monitor.sh +43 -0
  76. package/tests/test-system-dir-protection-guard.sh +81 -0
  77. package/tests/test-tool-retry-budget-guard.sh +75 -0
  78. package/tests/test-worktree-branch-pollution-detector.sh +50 -0
  79. package/tests/test-worktree-lifecycle-hooks.sh +29 -0
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ # broad-find-guard.sh — Block overly broad find/locate commands that scan the entire home directory
3
+ #
4
+ # Solves: Claude Code running `find $HOME` or `find /` which scans all user files,
5
+ # triggering OneDrive/iCloud/Dropbox to download synced files (#51010)
6
+ #
7
+ # Detects patterns like:
8
+ # find / -name "CLAUDE.md"
9
+ # find ~ -name "*.py"
10
+ # find $HOME -type f
11
+ # find /c/Users/username -name "*.md"
12
+ # locate "CLAUDE.md"
13
+ #
14
+ # Why: On Windows/macOS, cloud sync folders (OneDrive, iCloud, Dropbox) are
15
+ # under the home directory. A broad `find` traverses them and triggers
16
+ # downloading of ALL synced files — potentially gigabytes of data.
17
+ # Claude Code should only search project-scoped directories.
18
+ #
19
+ # Usage: Add to settings.json as a PreToolUse hook
20
+ #
21
+ # {
22
+ # "hooks": {
23
+ # "PreToolUse": [{
24
+ # "matcher": "Bash",
25
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/broad-find-guard.sh" }]
26
+ # }]
27
+ # }
28
+ # }
29
+ #
30
+ # TRIGGER: PreToolUse MATCHER: "Bash"
31
+
32
+ INPUT=$(cat)
33
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
34
+
35
+ [ -z "$COMMAND" ] && exit 0
36
+
37
+ # Pattern 1: find starting from root
38
+ if echo "$COMMAND" | grep -qE 'find\s+/\s'; then
39
+ echo "BLOCKED: find from root (/) scans entire filesystem. Use a project-scoped path instead." >&2
40
+ exit 2
41
+ fi
42
+
43
+ # Pattern 2: find starting from home directory (various forms)
44
+ if echo "$COMMAND" | grep -qE 'find\s+(~|\$HOME|/home/[a-zA-Z0-9_]+|/Users/[a-zA-Z0-9_]+|/c/Users/[a-zA-Z0-9_]+)\s'; then
45
+ echo "BLOCKED: find from home directory scans all user files including cloud sync folders (OneDrive/iCloud/Dropbox). Use a specific project path instead." >&2
46
+ exit 2
47
+ fi
48
+
49
+ # Pattern 3: find with -maxdepth 0 or 1 from home is OK (shallow), but deep scans are not
50
+ # This pattern catches find ~ without further path restriction
51
+ if echo "$COMMAND" | grep -qE 'find\s+(~|\$HOME|/home/|/Users/|/c/Users/)\b' && ! echo "$COMMAND" | grep -qE '\-maxdepth\s+[01]'; then
52
+ echo "BLOCKED: broad find from home directory. Add -maxdepth or use a specific subdirectory to avoid scanning cloud sync folders." >&2
53
+ exit 2
54
+ fi
55
+
56
+ # Pattern 4: locate without project-scoping (scans entire DB)
57
+ if echo "$COMMAND" | grep -qE '^\s*locate\s' && ! echo "$COMMAND" | grep -qE 'locate.*--database|locate.*-d\s'; then
58
+ echo "BLOCKED: locate searches the entire filesystem index. Use find with a specific directory instead." >&2
59
+ exit 2
60
+ fi
61
+
62
+ exit 0
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ # cache-creation-spike-detector.sh — cache_creationスパイクを検知して警告
3
+ # Why: smooshSystemReminderSiblings関数がsystem-reminderを毎ターンtool_result.contentに
4
+ # 折り込むことで、プロンプトキャッシュのプレフィックスが変わり、cache_creationが
5
+ # 数十万トークン単位でスパイクする (#49585)。5xの消費率上昇報告あり (#49593)
6
+ # Event: PostToolUse (全ツール実行後にチェック)
7
+ # Action: cache_creation_input_tokensが閾値を超えた場合に警告
8
+
9
+ INPUT=$(cat)
10
+ # PostToolUseではusageデータにアクセスできないため、
11
+ # /costコマンド出力のログファイルで累積cache_creationを追跡する
12
+ CACHE_LOG="/tmp/cc-cache-creation-tracker.log"
13
+ THRESHOLD=100000 # 100Kトークン以上でcache_creation警告
14
+
15
+ # 10回に1回だけチェック(パフォーマンス配慮)
16
+ COUNTER_FILE="/tmp/cache-spike-check-counter"
17
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
18
+ COUNT=$((COUNT + 1))
19
+ echo "$COUNT" > "$COUNTER_FILE"
20
+ [ $((COUNT % 10)) -ne 0 ] && exit 0
21
+
22
+ # セッション開始からの経過時間をチェック
23
+ SESSION_START=$(stat -c %Y /tmp/cache-spike-check-counter 2>/dev/null || echo "0")
24
+ NOW=$(date +%s)
25
+ ELAPSED=$((NOW - SESSION_START))
26
+
27
+ # セッション開始10分以内はスキップ(初期キャッシュ構築は正常)
28
+ [ "$ELAPSED" -lt 600 ] && exit 0
29
+
30
+ echo "INFO: cache_creationスパイク検知hookが動作中。異常なトークン消費を感じたら /cost で確認してください。" >&2
31
+ echo "詳細: https://github.com/anthropics/claude-code/issues/49585" >&2
32
+ exit 0
@@ -0,0 +1,96 @@
1
+ #!/bin/bash
2
+ # case-insensitive-path-guard.sh — Detect path case mismatches on case-insensitive filesystems
3
+ #
4
+ # Solves: Claude Code resolving paths with wrong case on macOS APFS (case-insensitive),
5
+ # causing rm -rf to destroy unintended directories
6
+ # - #48792: rm -rf WebstormProjects/ destroyed webstormprojects/ (10 years of work)
7
+ # - #49102: Same APFS bug, second catastrophic loss in 48 hours
8
+ #
9
+ # How it works:
10
+ # For rm/mv/cp commands targeting paths under $HOME, resolves the ACTUAL
11
+ # filesystem path and compares case. If the specified path exists only via
12
+ # case-insensitive matching (e.g., "Projects" matches "projects/"), blocks
13
+ # the command because the user likely intended a different directory.
14
+ #
15
+ # Platform: macOS only (APFS case-insensitive is default). On Linux (ext4, case-sensitive),
16
+ # the guard exits cleanly since case mismatches simply won't resolve.
17
+ #
18
+ # TRIGGER: PreToolUse MATCHER: "Bash"
19
+
20
+ set -euo pipefail
21
+
22
+ INPUT=$(cat)
23
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
24
+ [ -z "$COMMAND" ] && exit 0
25
+
26
+ # Only check destructive commands
27
+ echo "$COMMAND" | grep -qE '\b(rm|mv|cp)\s' || exit 0
28
+
29
+ # Skip if not macOS (Linux filesystems are case-sensitive by default)
30
+ if [ "$(uname)" != "Darwin" ]; then
31
+ exit 0
32
+ fi
33
+
34
+ # Extract target paths from destructive commands
35
+ # Handles: rm -rf path, mv path dest, cp -r path dest
36
+ TARGETS=""
37
+
38
+ # rm: all non-flag arguments
39
+ if echo "$COMMAND" | grep -qE '\brm\s'; then
40
+ TARGETS=$(echo "$COMMAND" | grep -oP '\brm\s+(-[a-zA-Z]+\s+)*\K[^\s;&|]+(\s+[^\s;&|]+)*' 2>/dev/null || true)
41
+ fi
42
+
43
+ # mv: first non-flag argument (source)
44
+ if echo "$COMMAND" | grep -qE '\bmv\s'; then
45
+ MV_TARGET=$(echo "$COMMAND" | grep -oP '\bmv\s+(-[a-zA-Z]+\s+)*\K\S+' 2>/dev/null || true)
46
+ TARGETS="$TARGETS $MV_TARGET"
47
+ fi
48
+
49
+ [ -z "$TARGETS" ] && exit 0
50
+
51
+ # Expand ~ to $HOME
52
+ TARGETS=$(echo "$TARGETS" | sed "s|~|$HOME|g")
53
+
54
+ for target in $TARGETS; do
55
+ # Skip flags
56
+ echo "$target" | grep -q '^-' && continue
57
+
58
+ # Skip safe disposable paths
59
+ echo "$target" | grep -qE '(node_modules|\.cache|__pycache__|/tmp/|dist/|build/)' && continue
60
+
61
+ # Only check paths under home directory (most risk)
62
+ echo "$target" | grep -qE "^($HOME|~)" || continue
63
+
64
+ # Resolve the canonical path using the filesystem
65
+ # On case-insensitive APFS, /Users/me/Projects resolves even if actual is /Users/me/projects
66
+ CANONICAL=$(python3 -c "
67
+ import os, sys
68
+ p = os.path.expanduser('$target')
69
+ # Walk up to find the longest existing prefix
70
+ check = p
71
+ while check and not os.path.exists(check):
72
+ check = os.path.dirname(check)
73
+ if not check:
74
+ sys.exit(0)
75
+ # Get the real path (canonical case)
76
+ real = os.path.realpath(check)
77
+ # Get the suffix that was beyond the existing part
78
+ suffix = p[len(check):]
79
+ print(real + suffix)
80
+ " 2>/dev/null || echo "")
81
+
82
+ [ -z "$CANONICAL" ] && continue
83
+
84
+ # Compare: if the specified path differs in case from the canonical path
85
+ if [ "$target" != "$CANONICAL" ] && [ "${target,,}" = "${CANONICAL,,}" ] 2>/dev/null; then
86
+ echo "BLOCKED: Case mismatch detected on case-insensitive filesystem" >&2
87
+ echo " Specified: $target" >&2
88
+ echo " Actual: $CANONICAL" >&2
89
+ echo " On macOS APFS, these resolve to the SAME directory." >&2
90
+ echo " This mismatch has caused catastrophic data loss (#48792, #49102)." >&2
91
+ echo " Verify the path case matches the actual filesystem before proceeding." >&2
92
+ exit 2
93
+ fi
94
+ done
95
+
96
+ exit 0
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # cjk-punctuation-guard.sh — Detect CJK punctuation corruption after Write/Edit
3
+ #
4
+ # Solves: Claude Code's Write and Edit tools silently convert full-width CJK
5
+ # punctuation to half-width ASCII (,→, 。→. 「→" etc.) without warning.
6
+ # This corrupts Chinese, Japanese, and Korean text.
7
+ # (GitHub Issue #50975)
8
+ #
9
+ # How it works:
10
+ # After Write or Edit, checks if the file contains CJK characters.
11
+ # If yes, runs git diff to see if any full-width punctuation was replaced
12
+ # with half-width equivalents in the same commit.
13
+ #
14
+ # TRIGGER: PostToolUse MATCHER: "Write|Edit"
15
+
16
+ INPUT=$(cat)
17
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.file // empty' 2>/dev/null)
18
+
19
+ [ -z "$FILE" ] || [ ! -f "$FILE" ] && exit 0
20
+
21
+ # Only check files that contain CJK characters
22
+ if ! grep -Pq '[\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]' "$FILE" 2>/dev/null; then
23
+ exit 0
24
+ fi
25
+
26
+ # Check git diff for punctuation changes (full-width → half-width)
27
+ DIFF=$(git diff -- "$FILE" 2>/dev/null)
28
+ [ -z "$DIFF" ] && exit 0
29
+
30
+ # Detect suspicious patterns: removed full-width, added half-width in same hunk
31
+ # Common corruptions: ,→, 。→. 、→, :→: ;→; !→! ?→? 「→" 」→" (→( )→)
32
+ REMOVED_FW=$(echo "$DIFF" | grep '^-' | grep -Pc '[,。、:;!?「」()【】『』]' 2>/dev/null || echo 0)
33
+ ADDED_HW=$(echo "$DIFF" | grep '^+' | grep -Pc '[,\.;:!\?\(\)\[\]]' 2>/dev/null || echo 0)
34
+
35
+ if [ "$REMOVED_FW" -gt 0 ] && [ "$ADDED_HW" -gt 0 ]; then
36
+ echo "⚠ WARNING: CJK punctuation may have been corrupted in $FILE" >&2
37
+ echo " $REMOVED_FW lines with full-width punctuation removed" >&2
38
+ echo " $ADDED_HW lines with half-width punctuation added" >&2
39
+ echo " Review: git diff -- \"$FILE\"" >&2
40
+ echo " Undo: git checkout -- \"$FILE\"" >&2
41
+ # Warning only (exit 0), not blocking — the write already happened
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ # clipboard-secret-guard.sh — Block secrets from being copied to clipboard
3
+ #
4
+ # Solves: Claude Code may pipe sensitive data (API keys, tokens, passwords)
5
+ # to clipboard utilities (pbcopy, xclip, xsel, wl-copy). This leaks
6
+ # secrets outside the terminal where they persist and may sync to cloud.
7
+ #
8
+ # How it works: PreToolUse hook that detects clipboard commands containing
9
+ # secret-like patterns. Blocks the operation.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Bash"
13
+ # CATEGORY: security
14
+
15
+ INPUT=$(cat)
16
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
17
+ [ -z "$CMD" ] && exit 0
18
+
19
+ # Check if command pipes to clipboard
20
+ if echo "$CMD" | grep -qiE '(pbcopy|xclip|xsel|wl-copy|clip\.exe)'; then
21
+ # Check if the piped content looks like it contains secrets
22
+ if echo "$CMD" | grep -qiE '(api.?key|secret|token|password|passwd|credential|private.?key|Bearer|AWS_|ANTHROPIC_|OPENAI_)'; then
23
+ echo "BLOCKED: Attempting to copy secret-like content to clipboard." >&2
24
+ echo " Clipboard data may sync to cloud or persist after session." >&2
25
+ exit 2
26
+ fi
27
+ fi
28
+
29
+ exit 0
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+ # context-size-alert.sh — Warn when context window usage exceeds threshold
3
+ # Trigger: PostToolUse
4
+ # Matcher: (empty — runs after every tool use)
5
+ #
6
+ # Since v2.1.100, the system prompt grew ~40-50% (~4,000 tokens).
7
+ # This hook monitors context usage and warns before you hit limits.
8
+ # See: https://github.com/anthropics/claude-code/issues/46339
9
+ #
10
+ # Optimization tips: https://zenn.dev/yurukusa/books/token-savings-guide
11
+ #
12
+ # TRIGGER: PostToolUse MATCHER: ""
13
+
14
+ INPUT=$(cat)
15
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
16
+
17
+ # Only check every 10th invocation to minimize overhead
18
+ COUNTER_FILE="/tmp/context-size-alert-${SESSION_ID:-default}.count"
19
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
20
+ COUNT=$((COUNT + 1))
21
+ echo "$COUNT" > "$COUNTER_FILE"
22
+ [ $((COUNT % 10)) -ne 0 ] && exit 0
23
+
24
+ # Check if /cost or /context data is available via transcript
25
+ TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
26
+ [ -z "$TRANSCRIPT" ] && exit 0
27
+
28
+ # Count approximate context by transcript file size (rough proxy)
29
+ if [ -f "$TRANSCRIPT" ]; then
30
+ SIZE_KB=$(du -k "$TRANSCRIPT" | cut -f1)
31
+ # Rough threshold: 500KB transcript ≈ approaching context limits
32
+ if [ "$SIZE_KB" -gt 500 ]; then
33
+ echo "⚠ Context is large (~${SIZE_KB}KB transcript). Consider /compact or /clear to reduce token cost." >&2
34
+ echo " Tip: Run /context to see exact usage breakdown." >&2
35
+ fi
36
+ fi
37
+
38
+ exit 0
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ # context-usage-drift-alert.sh — コンテキスト使用率の急増を検知
3
+ # Why: 1Mコンテキストモデルで実際124%使用中にUI上60%と表示される問題 (#50204)。
4
+ # 予告なくauto-compactが発火してコンテキストが消失する。
5
+ # ツール呼び出し回数でコンテキスト消費を推定し、警告する。
6
+ # Event: PostToolUse MATCHER: ""
7
+ # Action: セッション内のツール呼び出し回数が閾値を超えたら警告
8
+
9
+ COUNTER_FILE="/tmp/cc-context-usage-counter-$$"
10
+ # セッションPIDが変わると新しいカウンターになる
11
+ # フォールバック: 親PIDでグループ化
12
+ if [ ! -f "$COUNTER_FILE" ]; then
13
+ COUNTER_FILE="/tmp/cc-context-usage-counter-$(date +%Y%m%d)"
14
+ fi
15
+
16
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
17
+ COUNT=$((COUNT + 1))
18
+ echo "$COUNT" > "$COUNTER_FILE"
19
+
20
+ # 50回: 注意喚起、100回: 強い警告、150回: compact推奨
21
+ if [ "$COUNT" -eq 50 ]; then
22
+ echo "📊 Session checkpoint: $COUNT tool calls. Context may be growing large." >&2
23
+ echo "Run /cost to check actual usage. UI display may undercount by 2x (#50204)." >&2
24
+ elif [ "$COUNT" -eq 100 ]; then
25
+ echo "⚠ HIGH CONTEXT USAGE: $COUNT tool calls this session." >&2
26
+ echo "UI may show ~50% when actual usage is near 100%. Consider /compact." >&2
27
+ echo "Unexpected auto-compact can erase your working context. See: #50204" >&2
28
+ elif [ "$COUNT" -eq 150 ]; then
29
+ echo "🚨 VERY HIGH CONTEXT: $COUNT tool calls. Auto-compact likely imminent." >&2
30
+ echo "Save important state to files NOW. Run /compact manually to control what's kept." >&2
31
+ fi
32
+
33
+ exit 0
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # dangerous-pip-flag-guard.sh — Block dangerous pip flags that bypass safety
3
+ #
4
+ # Solves: Claude Code using pip install with dangerous flags in auto mode
5
+ # - #48992: pip install --break-system-packages passed through auto mode
6
+ # - Breaks system Python installations, can render OS tools unusable
7
+ #
8
+ # What it blocks:
9
+ # --break-system-packages (bypasses PEP 668 externally-managed check)
10
+ # --force-reinstall combined with system paths
11
+ # pip install targeting /usr/lib or /usr/local/lib directly
12
+ #
13
+ # What it allows:
14
+ # pip install in virtual environments (venv/conda)
15
+ # pip install --user (installs to user directory)
16
+ # pip install in project directories
17
+ #
18
+ # TRIGGER: PreToolUse MATCHER: "Bash"
19
+
20
+ set -euo pipefail
21
+
22
+ INPUT=$(cat)
23
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
24
+ [ -z "$COMMAND" ] && exit 0
25
+
26
+ # Only check pip commands
27
+ echo "$COMMAND" | grep -qE '\bpip[3]?\s+install\b' || exit 0
28
+
29
+ # Block --break-system-packages
30
+ if echo "$COMMAND" | grep -qE '\-\-break-system-packages'; then
31
+ echo "BLOCKED: --break-system-packages flag detected" >&2
32
+ echo "This flag bypasses PEP 668 protection and can break your system Python." >&2
33
+ echo "Use a virtual environment instead: python3 -m venv .venv && source .venv/bin/activate" >&2
34
+ exit 2
35
+ fi
36
+
37
+ # Block sudo pip install (system-wide install without venv)
38
+ if echo "$COMMAND" | grep -qE '\bsudo\s+pip[3]?\s+install\b'; then
39
+ echo "BLOCKED: sudo pip install detected" >&2
40
+ echo "System-wide pip install can break OS tools. Use a virtual environment." >&2
41
+ exit 2
42
+ fi
43
+
44
+ # Block pip targeting system directories
45
+ if echo "$COMMAND" | grep -qE '\bpip[3]?\s+install\b.*\-\-target\s*=?\s*/(usr|opt|lib)'; then
46
+ echo "BLOCKED: pip install targeting system directory" >&2
47
+ echo "Installing packages to system directories can break OS tools." >&2
48
+ exit 2
49
+ fi
50
+
51
+ exit 0
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+ # deny-bypass-detector.sh — Detect when Claude circumvents a hook denial
3
+ #
4
+ # Solves: After a PreToolUse hook blocks a command (exit 2), Claude
5
+ # reformulates the same operation as a script wrapper, eval, or
6
+ # bash -c to evade pattern matching. (#46991)
7
+ #
8
+ # How it works: Two-phase detection:
9
+ # Phase 1 (PostToolUse): When a Bash command is blocked (tool_result
10
+ # contains deny/block signals), log the dangerous substrings.
11
+ # Phase 2 (PreToolUse): Before each Bash command, check if it
12
+ # contains a recently-denied substring wrapped in bash -c, sh -c,
13
+ # eval, or a temp script.
14
+ #
15
+ # Denied commands expire after 60 seconds to avoid permanent lockout.
16
+ #
17
+ # Usage: Add TWO hooks — PostToolUse to log denials, PreToolUse to detect bypass
18
+ #
19
+ # {
20
+ # "hooks": {
21
+ # "PostToolUse": [{
22
+ # "matcher": "Bash",
23
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/deny-bypass-detector.sh" }]
24
+ # }],
25
+ # "PreToolUse": [{
26
+ # "matcher": "Bash",
27
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/deny-bypass-detector.sh" }]
28
+ # }]
29
+ # }
30
+ # }
31
+ #
32
+ # TRIGGER: PostToolUse+PreToolUse MATCHER: "Bash"
33
+
34
+ set -euo pipefail
35
+
36
+ DENY_LOG="/tmp/cc-deny-bypass-log"
37
+ mkdir -p "$(dirname "$DENY_LOG")" 2>/dev/null || true
38
+
39
+ INPUT=$(cat)
40
+ HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null)
41
+
42
+ # --- Phase 1: PostToolUse — log denied commands ---
43
+ if [[ "$HOOK_EVENT" == "PostToolUse" ]]; then
44
+ RESULT=$(echo "$INPUT" | jq -r '.tool_result // empty' 2>/dev/null)
45
+ # Detect denial signals in tool result
46
+ if echo "$RESULT" | grep -qiE 'BLOCKED|exit.*(code|status).*2|hook.*denied|hook.*blocked'; then
47
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
48
+ [ -z "$CMD" ] && exit 0
49
+ # Extract dangerous substrings: the core operation
50
+ # e.g., from "rm -rf node_modules" extract "rm -rf" and "node_modules"
51
+ TIMESTAMP=$(date +%s)
52
+ # Log the full command and key fragments
53
+ echo "${TIMESTAMP}|${CMD}" >> "$DENY_LOG"
54
+ # Also extract individual dangerous tokens
55
+ for token in $(echo "$CMD" | grep -oE '(rm\s+-rf|git\s+push\s+--force|git\s+reset\s+--hard|git\s+clean|chmod\s+777|curl.*\|.*sh|wget.*\|.*sh)' 2>/dev/null); do
56
+ echo "${TIMESTAMP}|PATTERN:${token}" >> "$DENY_LOG"
57
+ done
58
+ fi
59
+ exit 0
60
+ fi
61
+
62
+ # --- Phase 2: PreToolUse — detect bypass attempts ---
63
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
64
+ [ -z "$CMD" ] && exit 0
65
+ [ ! -f "$DENY_LOG" ] && exit 0
66
+
67
+ NOW=$(date +%s)
68
+ CUTOFF=$((NOW - 60))
69
+
70
+ # Clean expired entries
71
+ if [ -f "$DENY_LOG" ]; then
72
+ awk -F'|' -v cutoff="$CUTOFF" '$1 >= cutoff' "$DENY_LOG" > "${DENY_LOG}.tmp" 2>/dev/null
73
+ mv "${DENY_LOG}.tmp" "$DENY_LOG" 2>/dev/null || true
74
+ fi
75
+
76
+ # Check if current command wraps a denied command
77
+ BYPASS_DETECTED=0
78
+ DENIED_CMD=""
79
+
80
+ while IFS='|' read -r ts denied_cmd; do
81
+ [ "$ts" -lt "$CUTOFF" ] 2>/dev/null && continue
82
+ [ -z "$denied_cmd" ] && continue
83
+
84
+ # Skip pattern entries for this check
85
+ [[ "$denied_cmd" == PATTERN:* ]] && continue
86
+
87
+ # Check 1: bash -c / sh -c / eval wrapping the denied command
88
+ if echo "$CMD" | grep -qE '(bash|sh)\s+-c\s' || echo "$CMD" | grep -qE '\beval\s'; then
89
+ # Extract the inner command from the wrapper
90
+ INNER=$(echo "$CMD" | sed -E "s/.*(bash|sh)\s+-c\s+['\"]?//" | sed -E "s/['\"]?\s*$//")
91
+ # Check if inner command is similar to denied command
92
+ # Use key fragments: first word + arguments
93
+ DENIED_CORE=$(echo "$denied_cmd" | awk '{print $1}')
94
+ if echo "$INNER" | grep -qF "$DENIED_CORE"; then
95
+ BYPASS_DETECTED=1
96
+ DENIED_CMD="$denied_cmd"
97
+ break
98
+ fi
99
+ fi
100
+
101
+ # Check 2: Writing denied command to a temp script then executing
102
+ if echo "$CMD" | grep -qE '(cat|echo|printf).*>.*\.(sh|bash|tmp)' || \
103
+ echo "$CMD" | grep -qE 'python3?\s+-c|node\s+-e'; then
104
+ DENIED_CORE=$(echo "$denied_cmd" | awk '{print $1}')
105
+ if echo "$CMD" | grep -qF "$DENIED_CORE"; then
106
+ BYPASS_DETECTED=1
107
+ DENIED_CMD="$denied_cmd"
108
+ break
109
+ fi
110
+ fi
111
+
112
+ # Check 3: Direct re-execution (same command within 60s)
113
+ if [ "$CMD" = "$denied_cmd" ]; then
114
+ BYPASS_DETECTED=1
115
+ DENIED_CMD="$denied_cmd"
116
+ break
117
+ fi
118
+
119
+ done < "$DENY_LOG"
120
+
121
+ # Also check pattern-based detection
122
+ if [ "$BYPASS_DETECTED" -eq 0 ]; then
123
+ while IFS='|' read -r ts pattern_entry; do
124
+ [ "$ts" -lt "$CUTOFF" ] 2>/dev/null && continue
125
+ [[ "$pattern_entry" != PATTERN:* ]] && continue
126
+ PATTERN="${pattern_entry#PATTERN:}"
127
+ if echo "$CMD" | grep -qiE "$PATTERN"; then
128
+ BYPASS_DETECTED=1
129
+ DENIED_CMD="(pattern: $PATTERN)"
130
+ break
131
+ fi
132
+ done < "$DENY_LOG"
133
+ fi
134
+
135
+ if [ "$BYPASS_DETECTED" -eq 1 ]; then
136
+ echo "BLOCKED: Bypass attempt detected." >&2
137
+ echo " A similar command was denied <60 seconds ago: $DENIED_CMD" >&2
138
+ echo " Wrapping denied commands in scripts or eval does not change the policy." >&2
139
+ echo " Ask the user for explicit permission before retrying." >&2
140
+ exit 2
141
+ fi
142
+
143
+ exit 0
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ # dotenv-read-guard.sh — Block Read/Glob/Grep of .env files
3
+ #
4
+ # Solves: Sub-agents (especially Explore) reading .env files and exposing
5
+ # API keys, tokens, and secrets in the conversation transcript.
6
+ # (#51030 — Explore agent read .env, exposed 5 API keys, $50 damage)
7
+ # (#30731 — credentials exposed in output)
8
+ #
9
+ # This hook catches the Read tool accessing .env files, which
10
+ # credential-file-cat-guard.sh misses (it only covers Bash cat commands).
11
+ # Sub-agents inherit hooks but NOT memory/security instructions, making
12
+ # this hook essential for preventing secret leaks in multi-agent workflows.
13
+ #
14
+ # Usage: Add to settings.json as a PreToolUse hook
15
+ #
16
+ # {
17
+ # "hooks": {
18
+ # "PreToolUse": [{
19
+ # "matcher": "Read",
20
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/dotenv-read-guard.sh" }]
21
+ # }]
22
+ # }
23
+ # }
24
+ #
25
+ # TRIGGER: PreToolUse MATCHER: "Read"
26
+
27
+ INPUT=$(cat)
28
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
29
+
30
+ [ -z "$FILE_PATH" ] && exit 0
31
+
32
+ BASENAME=$(basename "$FILE_PATH")
33
+
34
+ # Block .env and all variants (.env.local, .env.production, .env.staging, etc.)
35
+ # Allow .env.example, .env.sample, .env.template (safe reference files)
36
+ if echo "$BASENAME" | grep -qE '^\.env(\.example|\.sample|\.template)$'; then
37
+ exit 0
38
+ fi
39
+
40
+ if echo "$BASENAME" | grep -qE '^\.env(\..+)?$'; then
41
+ echo "BLOCKED: Reading $BASENAME — contains secrets (API keys, tokens)" >&2
42
+ echo " .env files should never be read by Claude Code." >&2
43
+ echo " If you need to check which variables are set, read .env.example instead." >&2
44
+ echo " Related: GitHub Issue #51030 (sub-agent exposed 5 API keys)" >&2
45
+ exit 2
46
+ fi
47
+
48
+ exit 0
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+ # dotfile-protection-guard.sh — Block writes to critical dotfiles
3
+ #
4
+ # Solves: Claude Code modifying or truncating critical user dotfiles
5
+ # - #49615: Installer auto-update zeroed ~/.bash_profile and ~/.zshrc
6
+ # - #49539: ~/.git-credentials PATs deleted without confirmation
7
+ # - #49554: auto mode approved ~/.ssh deletion
8
+ #
9
+ # What it blocks (Write/Edit tool):
10
+ # ~/.bash_profile, ~/.bashrc, ~/.zshrc, ~/.profile
11
+ # ~/.ssh/*, ~/.git-credentials, ~/.gitconfig
12
+ # ~/.gnupg/*, ~/.npmrc (may contain auth tokens)
13
+ # ~/.aws/credentials, ~/.config/gh/hosts.yml
14
+ #
15
+ # What it allows:
16
+ # Files in project directories (not under ~/ root)
17
+ # ~/.claude/* (Claude Code's own config)
18
+ #
19
+ # TRIGGER: PreToolUse MATCHER: "Write|Edit"
20
+
21
+ set -euo pipefail
22
+
23
+ INPUT=$(cat)
24
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
25
+ [ -z "$FILE" ] && exit 0
26
+
27
+ # Expand ~ to actual home directory
28
+ HOME_DIR="$HOME"
29
+ RESOLVED=$(echo "$FILE" | sed "s|^~|$HOME_DIR|")
30
+
31
+ # Allow Claude Code's own config
32
+ if echo "$RESOLVED" | grep -qE "^${HOME_DIR}/\.claude(/|$)"; then
33
+ exit 0
34
+ fi
35
+
36
+ # Critical dotfiles — block any modification
37
+ CRITICAL_PATTERNS=(
38
+ "^${HOME_DIR}/\.(bash_profile|bashrc|zshrc|zshenv|profile|login|logout)$"
39
+ "^${HOME_DIR}/\.ssh(/|$)"
40
+ "^${HOME_DIR}/\.git-credentials$"
41
+ "^${HOME_DIR}/\.gitconfig$"
42
+ "^${HOME_DIR}/\.gnupg(/|$)"
43
+ "^${HOME_DIR}/\.npmrc$"
44
+ "^${HOME_DIR}/\.aws/(credentials|config)$"
45
+ "^${HOME_DIR}/\.config/gh/hosts\.yml$"
46
+ "^${HOME_DIR}/\.netrc$"
47
+ "^${HOME_DIR}/\.docker/config\.json$"
48
+ "^${HOME_DIR}/\.kube/config$"
49
+ )
50
+
51
+ for PATTERN in "${CRITICAL_PATTERNS[@]}"; do
52
+ if echo "$RESOLVED" | grep -qE "$PATTERN"; then
53
+ echo "BLOCKED: Modifying critical dotfile: $FILE" >&2
54
+ echo "This file contains shell config or credentials that should not be altered by AI." >&2
55
+ echo "If you need to modify this file, do it manually." >&2
56
+ exit 2
57
+ fi
58
+ done
59
+
60
+ exit 0