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.
- package/.claude-plugin/marketplace.json +66 -0
- package/.claude-plugin/plugin.json +11 -0
- package/README.md +133 -12
- package/SETTINGS_REFERENCE.md +2 -0
- package/SKILL.md +47 -0
- package/TROUBLESHOOTING.md +26 -0
- package/examples/README.md +11 -1
- package/examples/activity-logger.sh +58 -0
- package/examples/allow-claude-settings.sh +3 -2
- package/examples/allow-git-hooks-dir.sh +3 -2
- package/examples/allow-protected-dirs.sh +3 -2
- package/examples/auto-approve-compound-git.sh +3 -0
- package/examples/auto-compact-context-monitor.sh +35 -0
- package/examples/auto-mode-safety-enforcer.sh +57 -0
- package/examples/background-task-guard.sh +57 -0
- package/examples/bash-heuristic-approver.sh +1 -1
- package/examples/broad-find-guard.sh +62 -0
- package/examples/cache-creation-spike-detector.sh +32 -0
- package/examples/case-insensitive-path-guard.sh +96 -0
- package/examples/cjk-punctuation-guard.sh +44 -0
- package/examples/clipboard-secret-guard.sh +29 -0
- package/examples/context-size-alert.sh +38 -0
- package/examples/context-usage-drift-alert.sh +33 -0
- package/examples/dangerous-pip-flag-guard.sh +51 -0
- package/examples/decision-warn.sh +59 -0
- package/examples/deny-bypass-detector.sh +143 -0
- package/examples/direnv-auto-reload.sh +9 -2
- package/examples/dotenv-commit-guard.sh +11 -5
- package/examples/dotenv-read-guard.sh +48 -0
- package/examples/dotfile-protection-guard.sh +60 -0
- package/examples/effort-tracking-logger.sh +30 -0
- package/examples/financial-operation-guard.sh +47 -0
- package/examples/full-rewrite-detector.sh +63 -0
- package/examples/home-critical-bash-guard.sh +56 -0
- package/examples/idle-session-cost-alert.sh +36 -0
- package/examples/model-version-alert.sh +18 -0
- package/examples/model-version-change-alert.sh +31 -0
- package/examples/move-delete-sequence-guard.sh +92 -0
- package/examples/pii-upload-guard.sh +72 -0
- package/examples/pr-duplicate-guard.sh +14 -0
- package/examples/production-port-kill-guard.sh +60 -0
- package/examples/proof-log-session.sh +62 -0
- package/examples/quota-reset-cycle-monitor.sh +30 -0
- package/examples/repo-visibility-guard.sh +33 -0
- package/examples/sandbox-relative-path-audit.sh +51 -0
- package/examples/session-agent-cost-limiter.sh +43 -0
- package/examples/session-cost-alert.sh +62 -0
- package/examples/session-memory-watchdog.sh +9 -0
- package/examples/settings-integrity-monitor.sh +55 -0
- package/examples/settings-json-model-guard.sh +89 -0
- package/examples/shell-config-truncation-guard.sh +97 -0
- package/examples/shell-wrapper-guard.sh +4 -4
- package/examples/subagent-spawn-rate-monitor.sh +34 -0
- package/examples/subcommand-chain-guard.sh +44 -0
- package/examples/system-dir-protection-guard.sh +100 -0
- package/examples/thinking-display-enforcer.sh +25 -0
- package/examples/tool-retry-budget-guard.sh +59 -0
- package/examples/worktree-branch-pollution-detector.sh +35 -0
- package/examples/worktree-create-log.sh +6 -0
- package/examples/worktree-hook-linker.sh +72 -0
- package/examples/worktree-remove-uncommitted-guard.sh +20 -0
- package/hooks/hooks.json +60 -0
- package/index.mjs +108 -6
- package/memory/market-anthropic-japan-strategy-2026-04-13.md +4 -0
- package/package.json +2 -2
- package/plugins/credential-guard/.claude-plugin/plugin.json +58 -0
- package/plugins/git-protection/.claude-plugin/plugin.json +58 -0
- package/plugins/safety-essentials/.claude-plugin/plugin.json +58 -0
- package/plugins/token-guard/.claude-plugin/plugin.json +51 -0
- package/skills/safety-setup/SKILL.md +47 -0
- package/tests/dotenv-read-guard.test.sh +65 -0
- package/tests/test-auto-mode-safety-enforcer.sh +55 -0
- package/tests/test-case-insensitive-path-guard.sh +78 -0
- package/tests/test-context-usage-drift-alert.sh +52 -0
- package/tests/test-dangerous-pip-flag-guard.sh +56 -0
- package/tests/test-dotfile-protection-guard.sh +68 -0
- package/tests/test-effort-tracking-logger.sh +55 -0
- package/tests/test-financial-operation-guard.sh +59 -0
- package/tests/test-home-critical-bash-guard.sh +59 -0
- package/tests/test-model-version-change-alert.sh +55 -0
- package/tests/test-move-delete-sequence-guard.sh +63 -0
- package/tests/test-pr-duplicate-guard.sh +29 -0
- package/tests/test-quota-reset-cycle-monitor.sh +52 -0
- package/tests/test-shell-config-truncation-guard.sh +104 -0
- package/tests/test-subagent-spawn-rate-monitor.sh +43 -0
- package/tests/test-system-dir-protection-guard.sh +81 -0
- package/tests/test-tool-retry-budget-guard.sh +75 -0
- package/tests/test-worktree-branch-pollution-detector.sh +50 -0
- package/tests/test-worktree-lifecycle-hooks.sh +29 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# auto-compact-context-monitor.sh — Detect unexpected auto-compaction via context size drops
|
|
3
|
+
#
|
|
4
|
+
# PreCompact hooks do NOT fire on auto-compaction (only on manual /compact).
|
|
5
|
+
# This PostToolUse hook monitors for sudden context size drops that indicate
|
|
6
|
+
# auto-compaction occurred without PreCompact firing.
|
|
7
|
+
#
|
|
8
|
+
# Born from: https://github.com/anthropics/claude-code/issues/50467
|
|
9
|
+
# Related: https://github.com/anthropics/claude-code/issues/50492 (24% early fire)
|
|
10
|
+
#
|
|
11
|
+
# TRIGGER: PostToolUse MATCHER: ""
|
|
12
|
+
# Runs after every tool use to track context size changes.
|
|
13
|
+
|
|
14
|
+
INPUT=$(cat)
|
|
15
|
+
|
|
16
|
+
# Track context tokens (approximate via tool input size)
|
|
17
|
+
MONITOR_FILE="/tmp/cc-context-monitor-$$"
|
|
18
|
+
CURRENT_SIZE=$(echo "$INPUT" | wc -c)
|
|
19
|
+
|
|
20
|
+
if [ -f "$MONITOR_FILE" ]; then
|
|
21
|
+
PREV_SIZE=$(cat "$MONITOR_FILE")
|
|
22
|
+
# If current input is significantly smaller than previous (>50% drop),
|
|
23
|
+
# auto-compaction likely occurred
|
|
24
|
+
if [ "$PREV_SIZE" -gt 1000 ] && [ "$CURRENT_SIZE" -gt 0 ]; then
|
|
25
|
+
RATIO=$((CURRENT_SIZE * 100 / PREV_SIZE))
|
|
26
|
+
if [ "$RATIO" -lt 30 ]; then
|
|
27
|
+
echo "⚠ AUTO-COMPACTION DETECTED: Context dropped ${RATIO}% (${PREV_SIZE}→${CURRENT_SIZE} bytes)" >&2
|
|
28
|
+
echo " PreCompact hooks did NOT fire for this compaction (#50467)" >&2
|
|
29
|
+
echo " Important context may have been lost. Verify key facts." >&2
|
|
30
|
+
fi
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
echo "$CURRENT_SIZE" > "$MONITOR_FILE"
|
|
35
|
+
exit 0
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# auto-mode-safety-enforcer.sh — Block dangerous operations in auto/acceptEdits mode
|
|
3
|
+
#
|
|
4
|
+
# Solves: Auto mode safety classifier hardcoded to opus-4-6, fails with Opus 4.7
|
|
5
|
+
# - #49618: Safety classifier doesn't work with non-opus-4-6 models
|
|
6
|
+
# - #49554: auto mode approved ~/.ssh deletion
|
|
7
|
+
# - #18740: Auto-allow mode data loss without warning
|
|
8
|
+
#
|
|
9
|
+
# How it works: PreToolUse hook on Bash that blocks destructive commands
|
|
10
|
+
# regardless of which model or permission mode is active. Acts as a
|
|
11
|
+
# user-space safety net when the built-in classifier fails.
|
|
12
|
+
#
|
|
13
|
+
# What it blocks:
|
|
14
|
+
# - rm -rf on non-safe paths (/, ~, .., /home, /etc, /usr, /var, .git)
|
|
15
|
+
# - Credential file deletion (.ssh, .git-credentials, .env, .npmrc)
|
|
16
|
+
# - dd/mkfs/fdisk (disk operations)
|
|
17
|
+
# - kill -9 on system processes
|
|
18
|
+
# - chmod 777 on sensitive paths
|
|
19
|
+
#
|
|
20
|
+
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
INPUT=$(cat)
|
|
25
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
26
|
+
[ -z "$COMMAND" ] && exit 0
|
|
27
|
+
|
|
28
|
+
# --- Critical rm operations ---
|
|
29
|
+
if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|)(sudo\s+)?rm\s'; then
|
|
30
|
+
# Always block rm on root-level and home-level critical paths
|
|
31
|
+
if echo "$COMMAND" | grep -qE 'rm\s.*(/\s|/;|/$|~\/?\s|~\/?$|~\/\.|/home\b|/etc\b|/usr\b|/var\b|/opt\b|/root\b)'; then
|
|
32
|
+
echo "BLOCKED: rm targeting critical system/home path" >&2
|
|
33
|
+
echo "This operation would cause irreversible data loss." >&2
|
|
34
|
+
echo "Command: $COMMAND" >&2
|
|
35
|
+
exit 2
|
|
36
|
+
fi
|
|
37
|
+
# Block rm on dotfiles in home directory
|
|
38
|
+
if echo "$COMMAND" | grep -qE "rm\s.*(${HOME}|\~)/\."; then
|
|
39
|
+
echo "BLOCKED: rm targeting home dotfile" >&2
|
|
40
|
+
echo "Command: $COMMAND" >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# --- Disk-level operations ---
|
|
46
|
+
if echo "$COMMAND" | grep -qE '(^|\s)(sudo\s+)?(dd\s+.*of=/dev|mkfs\.|fdisk\s|parted\s)'; then
|
|
47
|
+
echo "BLOCKED: Disk-level operation (dd/mkfs/fdisk/parted)" >&2
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# --- Kill system processes ---
|
|
52
|
+
if echo "$COMMAND" | grep -qE 'kill\s+(-9\s+)?1$|killall\s+(init|systemd)'; then
|
|
53
|
+
echo "BLOCKED: Killing system process" >&2
|
|
54
|
+
exit 2
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
exit 0
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# background-task-guard.sh — Audit background Bash execution
|
|
3
|
+
#
|
|
4
|
+
# Solves: run_in_background:true on Bash tool skips the approval
|
|
5
|
+
# prompt, allowing dangerous commands to execute without user
|
|
6
|
+
# confirmation. (#46950)
|
|
7
|
+
#
|
|
8
|
+
# How it works: Checks if a Bash command is running in background
|
|
9
|
+
# mode. If the command matches dangerous patterns (destructive ops,
|
|
10
|
+
# network access, file deletion), blocks it. Background execution
|
|
11
|
+
# should only be used for safe, read-only operations.
|
|
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
|
+
# Check if this is a background execution
|
|
23
|
+
# Note: run_in_background is in tool_input for Bash
|
|
24
|
+
IS_BG=$(echo "$INPUT" | jq -r '.tool_input.run_in_background // false' 2>/dev/null)
|
|
25
|
+
[ "$IS_BG" != "true" ] && exit 0
|
|
26
|
+
|
|
27
|
+
# Background execution detected — apply strict safety rules
|
|
28
|
+
# Only allow read-only commands in background
|
|
29
|
+
|
|
30
|
+
# Block destructive operations
|
|
31
|
+
if echo "$CMD" | grep -qiE '\brm\s+-rf\b|\bgit\s+(push|reset|clean|checkout\s+--)\b|\bchmod\b|\bchown\b'; then
|
|
32
|
+
echo "BLOCKED: Destructive command not allowed in background mode." >&2
|
|
33
|
+
echo " Background tasks skip approval prompts — run this in foreground." >&2
|
|
34
|
+
exit 2
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Block network writes
|
|
38
|
+
if echo "$CMD" | grep -qiE 'curl\s+.*-X\s*(POST|PUT|PATCH|DELETE)|curl\s+.*--data|curl\s+.*-d\s|wget\s+.*--post'; then
|
|
39
|
+
echo "BLOCKED: Network write operation not allowed in background mode." >&2
|
|
40
|
+
echo " Background tasks skip approval prompts — run this in foreground." >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Block file writes to sensitive locations
|
|
45
|
+
if echo "$CMD" | grep -qiE '>\s*(/etc/|/usr/|/var/|~/.ssh/|~/.gnupg/|~/.claude/settings)'; then
|
|
46
|
+
echo "BLOCKED: Write to sensitive path not allowed in background mode." >&2
|
|
47
|
+
echo " Background tasks skip approval prompts — run this in foreground." >&2
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Block process killing
|
|
52
|
+
if echo "$CMD" | grep -qiE '\bkill\b|\bkillall\b|\bpkill\b'; then
|
|
53
|
+
echo "BLOCKED: Process termination not allowed in background mode." >&2
|
|
54
|
+
exit 2
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
exit 0
|
|
@@ -51,7 +51,7 @@ SAFE_COMMANDS="git|npm|npx|bun|yarn|pnpm|docker|make|cargo|go|pip|python3|node|t
|
|
|
51
51
|
BASE_CMD=$(echo "$COMMAND" | tr '\n' ' ' | sed 's/^[[:space:]]*//' | sed 's/^[A-Z_]*=[^ ]* //' | sed 's/^cd [^&;]* *[&;]* *//' | awk '{print $1}' | sed 's|.*/||')
|
|
52
52
|
|
|
53
53
|
if echo "$BASE_CMD" | grep -qE "^($SAFE_COMMANDS)$"; then
|
|
54
|
-
|
|
54
|
+
jq -n '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}'
|
|
55
55
|
exit 0
|
|
56
56
|
fi
|
|
57
57
|
|
|
@@ -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,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# decision-warn.sh — Warn and log irreversible operations
|
|
3
|
+
#
|
|
4
|
+
# Solves: "Why did Claude push to production?" — no decision trail for critical actions
|
|
5
|
+
# Detects dangerous operations (git push, rm -rf, database commands) and logs them
|
|
6
|
+
# to a decision log for post-incident analysis.
|
|
7
|
+
#
|
|
8
|
+
# Usage: Add to settings.json as a PostToolUse hook
|
|
9
|
+
#
|
|
10
|
+
# {
|
|
11
|
+
# "hooks": {
|
|
12
|
+
# "PostToolUse": [{
|
|
13
|
+
# "matcher": "Bash",
|
|
14
|
+
# "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/decision-warn.sh" }]
|
|
15
|
+
# }]
|
|
16
|
+
# }
|
|
17
|
+
# }
|
|
18
|
+
#
|
|
19
|
+
# Output: ~/.claude/decision-log.jsonl
|
|
20
|
+
# Each entry logs the command, timestamp, and detected risk category.
|
|
21
|
+
|
|
22
|
+
set -u
|
|
23
|
+
|
|
24
|
+
INPUT=$(cat)
|
|
25
|
+
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
26
|
+
[ "$TOOL" = "Bash" ] || exit 0
|
|
27
|
+
|
|
28
|
+
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
29
|
+
[ -z "$CMD" ] && exit 0
|
|
30
|
+
|
|
31
|
+
LOG_FILE="${HOME}/.claude/decision-log.jsonl"
|
|
32
|
+
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
33
|
+
|
|
34
|
+
RISK=""
|
|
35
|
+
|
|
36
|
+
# Detect irreversible operations
|
|
37
|
+
if echo "$CMD" | grep -qE 'git\s+push'; then
|
|
38
|
+
RISK="git-push"
|
|
39
|
+
elif echo "$CMD" | grep -qE 'git\s+reset\s+--hard'; then
|
|
40
|
+
RISK="git-reset-hard"
|
|
41
|
+
elif echo "$CMD" | grep -qE 'rm\s+(-rf|--recursive)'; then
|
|
42
|
+
RISK="destructive-delete"
|
|
43
|
+
elif echo "$CMD" | grep -qiE '(DROP\s+(TABLE|DATABASE)|TRUNCATE|DELETE\s+FROM)'; then
|
|
44
|
+
RISK="database-destructive"
|
|
45
|
+
elif echo "$CMD" | grep -qE 'npm\s+publish'; then
|
|
46
|
+
RISK="npm-publish"
|
|
47
|
+
elif echo "$CMD" | grep -qE 'curl\s+.*-X\s*(DELETE|PUT|POST)'; then
|
|
48
|
+
RISK="api-mutation"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
[ -z "$RISK" ] && exit 0
|
|
52
|
+
|
|
53
|
+
printf '{"ts":"%s","risk":"%s","cmd":"%s"}\n' \
|
|
54
|
+
"$TS" "$RISK" "$(echo "$CMD" | head -c 200 | tr '"' "'")" >> "$LOG_FILE"
|
|
55
|
+
|
|
56
|
+
echo "[DECISION] $RISK: $(echo "$CMD" | head -c 100)" >&2
|
|
57
|
+
echo " → Logged to: $LOG_FILE" >&2
|
|
58
|
+
|
|
59
|
+
exit 0
|