cc-safe-setup 29.6.39 → 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 (89) hide show
  1. package/.claude-plugin/marketplace.json +66 -0
  2. package/.claude-plugin/plugin.json +11 -0
  3. package/README.md +133 -12
  4. package/SETTINGS_REFERENCE.md +2 -0
  5. package/SKILL.md +47 -0
  6. package/TROUBLESHOOTING.md +26 -0
  7. package/examples/README.md +11 -1
  8. package/examples/activity-logger.sh +58 -0
  9. package/examples/allow-claude-settings.sh +3 -2
  10. package/examples/allow-git-hooks-dir.sh +3 -2
  11. package/examples/allow-protected-dirs.sh +3 -2
  12. package/examples/auto-approve-compound-git.sh +3 -0
  13. package/examples/auto-compact-context-monitor.sh +35 -0
  14. package/examples/auto-mode-safety-enforcer.sh +57 -0
  15. package/examples/background-task-guard.sh +57 -0
  16. package/examples/bash-heuristic-approver.sh +1 -1
  17. package/examples/broad-find-guard.sh +62 -0
  18. package/examples/cache-creation-spike-detector.sh +32 -0
  19. package/examples/case-insensitive-path-guard.sh +96 -0
  20. package/examples/cjk-punctuation-guard.sh +44 -0
  21. package/examples/clipboard-secret-guard.sh +29 -0
  22. package/examples/context-size-alert.sh +38 -0
  23. package/examples/context-usage-drift-alert.sh +33 -0
  24. package/examples/dangerous-pip-flag-guard.sh +51 -0
  25. package/examples/decision-warn.sh +59 -0
  26. package/examples/deny-bypass-detector.sh +143 -0
  27. package/examples/direnv-auto-reload.sh +9 -2
  28. package/examples/dotenv-commit-guard.sh +11 -5
  29. package/examples/dotenv-read-guard.sh +48 -0
  30. package/examples/dotfile-protection-guard.sh +60 -0
  31. package/examples/effort-tracking-logger.sh +30 -0
  32. package/examples/financial-operation-guard.sh +47 -0
  33. package/examples/full-rewrite-detector.sh +63 -0
  34. package/examples/home-critical-bash-guard.sh +56 -0
  35. package/examples/idle-session-cost-alert.sh +36 -0
  36. package/examples/model-version-alert.sh +18 -0
  37. package/examples/model-version-change-alert.sh +31 -0
  38. package/examples/move-delete-sequence-guard.sh +92 -0
  39. package/examples/pii-upload-guard.sh +72 -0
  40. package/examples/pr-duplicate-guard.sh +14 -0
  41. package/examples/production-port-kill-guard.sh +60 -0
  42. package/examples/proof-log-session.sh +62 -0
  43. package/examples/quota-reset-cycle-monitor.sh +30 -0
  44. package/examples/repo-visibility-guard.sh +33 -0
  45. package/examples/sandbox-relative-path-audit.sh +51 -0
  46. package/examples/session-agent-cost-limiter.sh +43 -0
  47. package/examples/session-cost-alert.sh +62 -0
  48. package/examples/session-memory-watchdog.sh +9 -0
  49. package/examples/settings-integrity-monitor.sh +55 -0
  50. package/examples/settings-json-model-guard.sh +89 -0
  51. package/examples/shell-config-truncation-guard.sh +97 -0
  52. package/examples/shell-wrapper-guard.sh +4 -4
  53. package/examples/subagent-spawn-rate-monitor.sh +34 -0
  54. package/examples/subcommand-chain-guard.sh +44 -0
  55. package/examples/system-dir-protection-guard.sh +100 -0
  56. package/examples/thinking-display-enforcer.sh +25 -0
  57. package/examples/tool-retry-budget-guard.sh +59 -0
  58. package/examples/worktree-branch-pollution-detector.sh +35 -0
  59. package/examples/worktree-create-log.sh +6 -0
  60. package/examples/worktree-hook-linker.sh +72 -0
  61. package/examples/worktree-remove-uncommitted-guard.sh +20 -0
  62. package/hooks/hooks.json +60 -0
  63. package/index.mjs +108 -6
  64. package/memory/market-anthropic-japan-strategy-2026-04-13.md +4 -0
  65. package/package.json +2 -2
  66. package/plugins/credential-guard/.claude-plugin/plugin.json +58 -0
  67. package/plugins/git-protection/.claude-plugin/plugin.json +58 -0
  68. package/plugins/safety-essentials/.claude-plugin/plugin.json +58 -0
  69. package/plugins/token-guard/.claude-plugin/plugin.json +51 -0
  70. package/skills/safety-setup/SKILL.md +47 -0
  71. package/tests/dotenv-read-guard.test.sh +65 -0
  72. package/tests/test-auto-mode-safety-enforcer.sh +55 -0
  73. package/tests/test-case-insensitive-path-guard.sh +78 -0
  74. package/tests/test-context-usage-drift-alert.sh +52 -0
  75. package/tests/test-dangerous-pip-flag-guard.sh +56 -0
  76. package/tests/test-dotfile-protection-guard.sh +68 -0
  77. package/tests/test-effort-tracking-logger.sh +55 -0
  78. package/tests/test-financial-operation-guard.sh +59 -0
  79. package/tests/test-home-critical-bash-guard.sh +59 -0
  80. package/tests/test-model-version-change-alert.sh +55 -0
  81. package/tests/test-move-delete-sequence-guard.sh +63 -0
  82. package/tests/test-pr-duplicate-guard.sh +29 -0
  83. package/tests/test-quota-reset-cycle-monitor.sh +52 -0
  84. package/tests/test-shell-config-truncation-guard.sh +104 -0
  85. package/tests/test-subagent-spawn-rate-monitor.sh +43 -0
  86. package/tests/test-system-dir-protection-guard.sh +81 -0
  87. package/tests/test-tool-retry-budget-guard.sh +75 -0
  88. package/tests/test-worktree-branch-pollution-detector.sh +50 -0
  89. package/tests/test-worktree-lifecycle-hooks.sh +29 -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
@@ -24,8 +24,15 @@ OLD_CWD=$(echo "$INPUT" | jq -r '.old_cwd // empty' 2>/dev/null)
24
24
  if [ -f "${NEW_CWD}/.envrc" ]; then
25
25
  echo "📂 Directory changed: found .envrc in ${NEW_CWD}" >&2
26
26
  if command -v direnv &>/dev/null; then
27
- echo " direnv: auto-allowing and loading" >&2
28
- cd "$NEW_CWD" && direnv allow . 2>/dev/null && eval "$(direnv export bash 2>/dev/null)"
27
+ # Write exports to CLAUDE_ENV_FILE so Claude Code picks them up
28
+ # (eval in a subshell would be lost CLAUDE_ENV_FILE persists to BashTool)
29
+ if [ -n "$CLAUDE_ENV_FILE" ]; then
30
+ echo " direnv: auto-allowing and writing to CLAUDE_ENV_FILE" >&2
31
+ cd "$NEW_CWD" && direnv allow . 2>/dev/null && \
32
+ direnv export bash > "$CLAUDE_ENV_FILE" 2>/dev/null
33
+ else
34
+ echo " ⚠ CLAUDE_ENV_FILE not set — direnv exports won't persist" >&2
35
+ fi
29
36
  else
30
37
  echo " ⚠ direnv not installed — .envrc found but not loaded" >&2
31
38
  fi
@@ -28,15 +28,21 @@ if echo "$COMMAND" | grep -qE 'git\s+add\s+.*\.env'; then
28
28
  exit 2
29
29
  fi
30
30
 
31
- # Check for git add -A/-f that might include .env
32
- if echo "$COMMAND" | grep -qE 'git\s+add\s+(-A|--all|-f|--force)\b'; then
33
- # Check if .env exists in the working directory
31
+ # Check for git add -f/--force (bypasses .gitignore can stage secrets)
32
+ # GitHub Issue anthropics/claude-code#44730: auto-mode used git add -f to
33
+ # force-add .gitignore'd secret files, exposing credentials in a commit.
34
+ if echo "$COMMAND" | grep -qE 'git\s+add\s+.*(-f|--force)\b'; then
35
+ echo "BLOCKED: 'git add --force' bypasses .gitignore and can stage secret files." >&2
36
+ echo " Use 'git add <specific-file>' without --force instead." >&2
37
+ exit 2
38
+ fi
39
+
40
+ # Check for git add -A/--all that might include .env
41
+ if echo "$COMMAND" | grep -qE 'git\s+add\s+(-A|--all)\b'; then
34
42
  if [ -f ".env" ] || [ -f ".env.local" ] || [ -f ".env.production" ]; then
35
- # Check if .gitignore excludes it
36
43
  if ! git check-ignore -q .env 2>/dev/null; then
37
44
  echo "WARNING: 'git add -A' with .env file not in .gitignore." >&2
38
45
  echo " Add .env to .gitignore before staging all files." >&2
39
- # Warning only for git add -A
40
46
  fi
41
47
  fi
42
48
  fi
@@ -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
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ # effort-tracking-logger.sh — ツール使用ごとのエフォートログを記録
3
+ # Why: OTEL互換のエフォート追跡への要望が急増 (#49893, 18👍)。
4
+ # 公式対応を待たずに、hookでツール呼び出しごとのログを残す。
5
+ # コスト分析・セッション振り返り・ボトルネック特定に使える。
6
+ # Event: PostToolUse MATCHER: ""
7
+ # Output: ~/.claude/effort-log/YYYY-MM-DD.jsonl
8
+
9
+ LOG_DIR="${HOME}/.claude/effort-log"
10
+ mkdir -p "$LOG_DIR"
11
+ LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d).jsonl"
12
+
13
+ # stdinからツール情報を取得
14
+ TOOL_INPUT=$(cat)
15
+ TOOL_NAME=$(echo "$TOOL_INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name','unknown'))" 2>/dev/null)
16
+ TOOL_STATUS=$(echo "$TOOL_INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('was_error','false'))" 2>/dev/null)
17
+
18
+ # JSONLログに追記
19
+ python3 -c "
20
+ import json, datetime
21
+ entry = {
22
+ 'timestamp': datetime.datetime.now().isoformat(),
23
+ 'tool': '$TOOL_NAME',
24
+ 'error': '$TOOL_STATUS' == 'true',
25
+ 'session_pid': $(echo $$)
26
+ }
27
+ print(json.dumps(entry))
28
+ " >> "$LOG_FILE"
29
+
30
+ exit 0
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # financial-operation-guard.sh — Block unauthorized financial operations
3
+ #
4
+ # Solves: Claude Code transferred $1,446 from spot to futures without
5
+ # authorization when told to "close a position". Financial APIs
6
+ # should never be called without explicit per-transaction approval. (#46828)
7
+ #
8
+ # How it works: Detects commands that interact with exchange APIs,
9
+ # wallet transfers, payment processors, or any operation involving
10
+ # fund movement. Blocks with exit 2 and requires explicit user
11
+ # confirmation.
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Bash"
15
+
16
+ set -euo pipefail
17
+
18
+ INPUT=$(cat)
19
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
20
+ [ -z "$CMD" ] && exit 0
21
+
22
+ # Detect financial API calls
23
+ # Exchange APIs
24
+ if echo "$CMD" | grep -qiE '(binance|bitget|bybit|kraken|coinbase|ftx|okx|kucoin|gate\.io|huobi).*(transfer|withdraw|swap|order|trade|margin|futures|spot|deposit)'; then
25
+ echo "BLOCKED: Financial exchange operation detected." >&2
26
+ echo " Command: $(echo "$CMD" | head -c 200)" >&2
27
+ echo " Fund transfers require explicit user approval for EACH transaction." >&2
28
+ exit 2
29
+ fi
30
+
31
+ # Generic payment/transfer patterns
32
+ if echo "$CMD" | grep -qiE '(transfer|withdraw|send|swap|bridge)[^a-z].*\b(usdt|usdc|eth|btc|sol|bnb|funds|balance|wallet)\b'; then
33
+ echo "BLOCKED: Cryptocurrency transfer operation detected." >&2
34
+ echo " Command: $(echo "$CMD" | head -c 200)" >&2
35
+ echo " Wallet/fund operations require explicit user approval." >&2
36
+ exit 2
37
+ fi
38
+
39
+ # Payment processor APIs
40
+ if echo "$CMD" | grep -qiE 'stripe.*(charges?|transfers?|payouts?)|paypal.*(payments?|send|transfers?)|square.*(payments?|charges?)'; then
41
+ echo "BLOCKED: Payment processor operation detected." >&2
42
+ echo " Command: $(echo "$CMD" | head -c 200)" >&2
43
+ echo " Payment operations require explicit user approval." >&2
44
+ exit 2
45
+ fi
46
+
47
+ exit 0
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # full-rewrite-detector.sh — Warn on full-file rewrites
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes rewrites entire files when a small edit
7
+ # would suffice. AMD's analysis of 6,852 sessions found this
8
+ # pattern increasing over time — a sign of quality degradation.
9
+ # This hook detects when >80% of a file's lines were changed
10
+ # and warns the user.
11
+ #
12
+ # TRIGGER: PostToolUse
13
+ # MATCHER: "Write"
14
+ #
15
+ # HOW IT WORKS:
16
+ # After a Write operation, checks git diff for the target file.
17
+ # If the ratio of changed lines to total lines exceeds the
18
+ # threshold (default 80%), emits a warning.
19
+ #
20
+ # CONFIGURATION:
21
+ # CC_REWRITE_THRESHOLD=80 — percentage threshold (default 80)
22
+ #
23
+ # NOTE: Only works in git repositories. No-op outside git repos.
24
+ # ================================================================
25
+
26
+ INPUT=$(cat)
27
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
28
+
29
+ # Skip if no file path
30
+ [ -z "$FILE" ] && exit 0
31
+
32
+ # Skip if not in a git repo
33
+ git rev-parse --is-inside-work-tree &>/dev/null || exit 0
34
+
35
+ # Skip if file doesn't exist (new file creation is fine)
36
+ [ -f "$FILE" ] || exit 0
37
+
38
+ # Get change stats from git
39
+ STATS=$(git diff --numstat -- "$FILE" 2>/dev/null)
40
+ [ -z "$STATS" ] && exit 0
41
+
42
+ ADDED=$(echo "$STATS" | awk '{print $1}')
43
+ DELETED=$(echo "$STATS" | awk '{print $2}')
44
+
45
+ # Handle binary files (git outputs "-" for binary)
46
+ [ "$ADDED" = "-" ] && exit 0
47
+
48
+ CHANGED=$((ADDED + DELETED))
49
+ TOTAL=$(wc -l < "$FILE" 2>/dev/null || echo 0)
50
+
51
+ # Avoid division by zero; skip very small files
52
+ [ "$TOTAL" -lt 5 ] && exit 0
53
+
54
+ RATIO=$((CHANGED * 100 / TOTAL))
55
+ THRESHOLD=${CC_REWRITE_THRESHOLD:-80}
56
+
57
+ if [ "$RATIO" -gt "$THRESHOLD" ]; then
58
+ echo "WARNING: Full rewrite detected on $(basename "$FILE")" >&2
59
+ echo " ${RATIO}% of lines changed (${CHANGED} lines changed / ${TOTAL} total)" >&2
60
+ echo " Consider: was a partial edit sufficient?" >&2
61
+ fi
62
+
63
+ exit 0
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # home-critical-bash-guard.sh — Block Bash commands that delete/modify critical home files
3
+ #
4
+ # Solves: Bash commands that rm/mv/truncate critical dotfiles and directories
5
+ # - #49554: auto mode approved ~/.ssh directory deletion
6
+ # - #49539: ~/.git-credentials PATs deleted without confirmation
7
+ # - #49464: ./~ misinterpreted as ~/ leading to home directory deletion attempt
8
+ #
9
+ # Complements dotfile-protection-guard.sh (which covers Write/Edit tools).
10
+ # This hook covers the Bash tool path — rm, mv, truncate, > redirect on dotfiles.
11
+ #
12
+ # TRIGGER: PreToolUse MATCHER: "Bash"
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT=$(cat)
17
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
18
+ [ -z "$COMMAND" ] && exit 0
19
+
20
+ HOME_DIR="$HOME"
21
+
22
+ # Critical paths (regex patterns)
23
+ CRITICAL="(${HOME_DIR}|\~)/\.(bashrc|bash_profile|zshrc|zshenv|profile|login|logout|ssh|git-credentials|gitconfig|gnupg|npmrc|netrc|docker|kube|aws)"
24
+
25
+ # Check for rm/unlink targeting critical paths
26
+ if echo "$COMMAND" | grep -qE "(rm|unlink)\s" && echo "$COMMAND" | grep -qE "$CRITICAL"; then
27
+ echo "BLOCKED: Deleting critical home directory file" >&2
28
+ echo "Command: $COMMAND" >&2
29
+ exit 2
30
+ fi
31
+
32
+ # Check for mv (rename/move) of critical paths
33
+ if echo "$COMMAND" | grep -qE "mv\s" && echo "$COMMAND" | grep -qE "$CRITICAL"; then
34
+ echo "BLOCKED: Moving/renaming critical home directory file" >&2
35
+ echo "Command: $COMMAND" >&2
36
+ exit 2
37
+ fi
38
+
39
+ # Check for truncation via redirect (> ~/.bashrc or : > ~/.bashrc)
40
+ if echo "$COMMAND" | grep -qE ">\s*(${HOME_DIR}|\~)/\."; then
41
+ TARGET=$(echo "$COMMAND" | grep -oP ">\s*\K(${HOME_DIR}|~)/\.[^\s;|&]+")
42
+ if echo "$TARGET" | grep -qE "$CRITICAL"; then
43
+ echo "BLOCKED: Truncating critical home directory file" >&2
44
+ echo "Command: $COMMAND" >&2
45
+ exit 2
46
+ fi
47
+ fi
48
+
49
+ # Check for chmod on critical credential files
50
+ if echo "$COMMAND" | grep -qE "chmod\s.*777" && echo "$COMMAND" | grep -qE "$CRITICAL"; then
51
+ echo "BLOCKED: Removing permissions on critical file" >&2
52
+ echo "Command: $COMMAND" >&2
53
+ exit 2
54
+ fi
55
+
56
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ # idle-session-cost-alert.sh — Warn when session has been idle too long
3
+ # An idle session can still consume tokens via background processes.
4
+ # Incident: #50389 — Idle session consumed 18% usage limit over 2 hours with zero user input.
5
+ #
6
+ # This hook runs on Notification events and warns if the session has been
7
+ # idle for more than 5 minutes, reminding the user to exit if not actively working.
8
+ #
9
+ # Hook config (settings.json):
10
+ # {
11
+ # "hooks": {
12
+ # "Notification": [{
13
+ # "matcher": "",
14
+ # "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/idle-session-cost-alert.sh" }]
15
+ # }]
16
+ # }
17
+ # }
18
+
19
+ INPUT=$(cat)
20
+
21
+ # Track last activity timestamp
22
+ IDLE_FILE="/tmp/claude-idle-tracker-$$"
23
+ CURRENT_TIME=$(date +%s)
24
+
25
+ if [ -f "$IDLE_FILE" ]; then
26
+ LAST_ACTIVE=$(cat "$IDLE_FILE")
27
+ IDLE_SECONDS=$((CURRENT_TIME - LAST_ACTIVE))
28
+
29
+ if [ "$IDLE_SECONDS" -gt 300 ]; then
30
+ IDLE_MINUTES=$((IDLE_SECONDS / 60))
31
+ echo "WARNING: Session idle for ${IDLE_MINUTES} minutes. Idle sessions can consume tokens via background processes (#50389). Consider exiting if not actively working." >&2
32
+ fi
33
+ fi
34
+
35
+ echo "$CURRENT_TIME" > "$IDLE_FILE"
36
+ exit 0
@@ -0,0 +1,18 @@
1
+ INPUT=$(cat)
2
+ COUNTER_FILE="/tmp/.cc-model-check-counter"
3
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
4
+ COUNT=$((COUNT + 1))
5
+ echo "$COUNT" > "$COUNTER_FILE"
6
+ if [ $((COUNT % 50)) -ne 0 ]; then
7
+ exit 0
8
+ fi
9
+ SESSION_FILE=$(ls -t ~/.claude/projects/*/session.jsonl 2>/dev/null | head -1)
10
+ if [ -n "$SESSION_FILE" ]; then
11
+ MODEL=$(grep -o '"model":"[^"]*"' "$SESSION_FILE" 2>/dev/null | tail -1 | cut -d'"' -f4)
12
+ if echo "$MODEL" | grep -qi "opus-4-7\|opus-4.7"; then
13
+ echo "⚠ Model alert: You're using $MODEL which may consume 3x more tokens than Opus 4.6."
14
+ echo "Consider: claude --model claude-opus-4-6 or add \"model\": \"claude-opus-4-6\" to settings.json"
15
+ echo "See: https://github.com/anthropics/claude-code/issues/49601"
16
+ fi
17
+ fi
18
+ exit 0
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # model-version-change-alert.sh — モデルバージョン変更を検知して警告
3
+ # Why: Opus 4.6がモデルピッカーから突然削除された (#49689, 14👍)。
4
+ # ユーザーが意図せず別モデルに切り替えられるケースが多発。
5
+ # モデルが変わるとhookの挙動・トークン消費・品質が全て変わる。
6
+ # Event: Notification MATCHER: ""
7
+ # Action: 前回のモデルと現在のモデルを比較し、変更時に警告
8
+
9
+ MODEL_HISTORY="/tmp/cc-model-version-history"
10
+ CURRENT_MODEL="${CLAUDE_MODEL:-unknown}"
11
+
12
+ # Notificationイベントのbodyからモデル情報を取得試行
13
+ if [ -n "$1" ]; then
14
+ BODY_MODEL=$(echo "$1" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('model',''))" 2>/dev/null)
15
+ [ -n "$BODY_MODEL" ] && CURRENT_MODEL="$BODY_MODEL"
16
+ fi
17
+
18
+ # 前回のモデルを読み取り
19
+ PREV_MODEL=$(cat "$MODEL_HISTORY" 2>/dev/null || echo "")
20
+
21
+ if [ -n "$PREV_MODEL" ] && [ "$PREV_MODEL" != "$CURRENT_MODEL" ] && [ "$CURRENT_MODEL" != "unknown" ]; then
22
+ echo "⚠ MODEL CHANGED: $PREV_MODEL → $CURRENT_MODEL" >&2
23
+ echo "Your model was switched. This affects token consumption, quality, and hook behavior." >&2
24
+ echo "If unintended, check your settings: claude --model $PREV_MODEL" >&2
25
+ echo "Known issue: Opus 4.6 was removed from the Desktop picker (#49689)" >&2
26
+ fi
27
+
28
+ # 現在のモデルを記録
29
+ [ "$CURRENT_MODEL" != "unknown" ] && echo "$CURRENT_MODEL" > "$MODEL_HISTORY"
30
+
31
+ exit 0