cc-safe-setup 29.6.1 → 29.6.2
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/examples/bash-trace-guard.sh +48 -0
- package/examples/post-compact-safety.sh +61 -0
- package/examples/read-budget-guard.sh +63 -0
- package/examples/session-drift-guard.sh +73 -0
- package/examples/strip-coauthored-by.sh +46 -0
- package/examples/variable-expansion-guard.sh +51 -0
- package/index.mjs +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# bash-trace-guard.sh — Block debug tracing that exposes secrets
|
|
3
|
+
#
|
|
4
|
+
# Solves: Claude running bash -x which traces all commands including
|
|
5
|
+
# expanded secrets from .env files, and SQL queries for credential columns.
|
|
6
|
+
# See: https://github.com/anthropics/claude-code/issues/37599
|
|
7
|
+
#
|
|
8
|
+
# TRIGGER: PreToolUse
|
|
9
|
+
# MATCHER: Bash
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# {
|
|
13
|
+
# "hooks": {
|
|
14
|
+
# "PreToolUse": [{
|
|
15
|
+
# "matcher": "Bash",
|
|
16
|
+
# "hooks": [{
|
|
17
|
+
# "type": "command",
|
|
18
|
+
# "command": "~/.claude/hooks/bash-trace-guard.sh"
|
|
19
|
+
# }]
|
|
20
|
+
# }]
|
|
21
|
+
# }
|
|
22
|
+
# }
|
|
23
|
+
|
|
24
|
+
INPUT=$(cat)
|
|
25
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
26
|
+
[ -z "$COMMAND" ] && exit 0
|
|
27
|
+
|
|
28
|
+
# Block bash debug tracing (expands all variables including secrets)
|
|
29
|
+
if echo "$COMMAND" | grep -qE 'bash\s+(-[a-z]*x|-x)|set\s+-x|set\s+-o\s+xtrace|bash\s+--debug'; then
|
|
30
|
+
echo "BLOCKED: Debug tracing exposes expanded variables including secrets" >&2
|
|
31
|
+
echo "Use 'echo' statements for debugging instead of bash -x." >&2
|
|
32
|
+
exit 2
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Block source .env followed by echo/printenv (secret exfiltration)
|
|
36
|
+
if echo "$COMMAND" | grep -qE '(source|\.)\s+\.env.*&&.*(echo|printenv|env|set\b)'; then
|
|
37
|
+
echo "BLOCKED: Sourcing .env and printing environment exposes secrets" >&2
|
|
38
|
+
exit 2
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Block SQL queries targeting credential columns
|
|
42
|
+
if echo "$COMMAND" | grep -qiE 'SELECT.*\b(password|secret|token|api_key|private_key|access_key)\b.*FROM'; then
|
|
43
|
+
echo "BLOCKED: SQL query targets credential columns" >&2
|
|
44
|
+
echo "Query non-sensitive columns, or use application-level access." >&2
|
|
45
|
+
exit 2
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
exit 0
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# post-compact-safety.sh — Guard against autonomous actions after compaction
|
|
3
|
+
#
|
|
4
|
+
# Solves: After context compaction, Claude interprets the summary as
|
|
5
|
+
# authorization to push commits and make irreversible changes without
|
|
6
|
+
# user approval.
|
|
7
|
+
# See: https://github.com/anthropics/claude-code/issues/39912
|
|
8
|
+
#
|
|
9
|
+
# TRIGGER: PreToolUse
|
|
10
|
+
# MATCHER: Bash
|
|
11
|
+
#
|
|
12
|
+
# After compaction, blocks git push and other irreversible commands
|
|
13
|
+
# for the first N tool calls, requiring explicit user interaction first.
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# {
|
|
17
|
+
# "hooks": {
|
|
18
|
+
# "PreToolUse": [{
|
|
19
|
+
# "matcher": "Bash",
|
|
20
|
+
# "hooks": [{
|
|
21
|
+
# "type": "command",
|
|
22
|
+
# "command": "~/.claude/hooks/post-compact-safety.sh"
|
|
23
|
+
# }]
|
|
24
|
+
# }]
|
|
25
|
+
# }
|
|
26
|
+
# }
|
|
27
|
+
#
|
|
28
|
+
# Config: CC_POST_COMPACT_GUARD=10 (block for first 10 calls after compact)
|
|
29
|
+
|
|
30
|
+
INPUT=$(cat)
|
|
31
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
32
|
+
[ -z "$COMMAND" ] && exit 0
|
|
33
|
+
|
|
34
|
+
GUARD_CALLS=${CC_POST_COMPACT_GUARD:-10}
|
|
35
|
+
MARKER="/tmp/cc-post-compact-$(whoami)"
|
|
36
|
+
COUNTER="/tmp/cc-post-compact-count-$(whoami)"
|
|
37
|
+
|
|
38
|
+
# Detect compaction (context_window field or session state change)
|
|
39
|
+
# After compaction, the session summary often contains these patterns
|
|
40
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
41
|
+
|
|
42
|
+
# Check if we're in post-compact guard mode
|
|
43
|
+
if [ -f "$MARKER" ]; then
|
|
44
|
+
COUNT=1
|
|
45
|
+
[ -f "$COUNTER" ] && COUNT=$(( $(cat "$COUNTER") + 1 ))
|
|
46
|
+
echo "$COUNT" > "$COUNTER"
|
|
47
|
+
|
|
48
|
+
if [ "$COUNT" -le "$GUARD_CALLS" ]; then
|
|
49
|
+
# Block irreversible commands during guard period
|
|
50
|
+
if echo "$COMMAND" | grep -qE '^\s*(git\s+push|git\s+reset|git\s+clean|npm\s+publish|docker\s+push)'; then
|
|
51
|
+
echo "BLOCKED: Irreversible command blocked (post-compaction safety)" >&2
|
|
52
|
+
echo " $COUNT/$GUARD_CALLS guard calls remaining. Confirm with user first." >&2
|
|
53
|
+
exit 2
|
|
54
|
+
fi
|
|
55
|
+
else
|
|
56
|
+
# Guard period over
|
|
57
|
+
rm -f "$MARKER" "$COUNTER"
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
exit 0
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# read-budget-guard.sh — Limit excessive file reading to prevent token waste
|
|
3
|
+
#
|
|
4
|
+
# Solves: Claude reading far more files than necessary, consuming 25% of
|
|
5
|
+
# quota before any real work begins. Prevents duplicate reads.
|
|
6
|
+
# See: https://github.com/anthropics/claude-code/issues/38733
|
|
7
|
+
#
|
|
8
|
+
# TRIGGER: PreToolUse
|
|
9
|
+
# MATCHER: Read
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# {
|
|
13
|
+
# "hooks": {
|
|
14
|
+
# "PreToolUse": [{
|
|
15
|
+
# "matcher": "Read",
|
|
16
|
+
# "hooks": [{
|
|
17
|
+
# "type": "command",
|
|
18
|
+
# "command": "~/.claude/hooks/read-budget-guard.sh"
|
|
19
|
+
# }]
|
|
20
|
+
# }]
|
|
21
|
+
# }
|
|
22
|
+
# }
|
|
23
|
+
#
|
|
24
|
+
# Config via env vars:
|
|
25
|
+
# CC_READ_BUDGET=100 — max unique files per session (default: 100)
|
|
26
|
+
# CC_READ_WARN=50 — warn threshold (default: 50)
|
|
27
|
+
|
|
28
|
+
INPUT=$(cat)
|
|
29
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
30
|
+
[ -z "$FILE" ] && exit 0
|
|
31
|
+
|
|
32
|
+
BUDGET=${CC_READ_BUDGET:-100}
|
|
33
|
+
WARN=${CC_READ_WARN:-50}
|
|
34
|
+
TRACKER="/tmp/cc-read-budget-$$"
|
|
35
|
+
|
|
36
|
+
# Create tracker if needed
|
|
37
|
+
[ -f "$TRACKER" ] || touch "$TRACKER"
|
|
38
|
+
|
|
39
|
+
# Check for duplicate read
|
|
40
|
+
if grep -qxF "$FILE" "$TRACKER" 2>/dev/null; then
|
|
41
|
+
echo "⚠ Duplicate read: $(basename "$FILE") was already read this session" >&2
|
|
42
|
+
echo " Consider using the cached content instead of re-reading." >&2
|
|
43
|
+
# Allow but warn (don't block duplicate reads, just flag them)
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Track this read
|
|
47
|
+
echo "$FILE" >> "$TRACKER"
|
|
48
|
+
COUNT=$(wc -l < "$TRACKER")
|
|
49
|
+
|
|
50
|
+
# Check budget
|
|
51
|
+
if [ "$COUNT" -gt "$BUDGET" ]; then
|
|
52
|
+
echo "BLOCKED: Read budget exceeded ($COUNT/$BUDGET files)" >&2
|
|
53
|
+
echo "You've read $COUNT unique files this session." >&2
|
|
54
|
+
echo "Start working with what you have, or increase CC_READ_BUDGET." >&2
|
|
55
|
+
exit 2
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [ "$COUNT" -eq "$WARN" ]; then
|
|
59
|
+
echo "⚠ Read budget warning: $COUNT/$BUDGET files read" >&2
|
|
60
|
+
echo " Focus on implementation rather than reading more files." >&2
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
exit 0
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# session-drift-guard.sh — Progressive safety as session ages
|
|
3
|
+
#
|
|
4
|
+
# Solves: After ~6 hours, Claude starts ignoring CLAUDE.md rules,
|
|
5
|
+
# acting autonomously, creating duplicates, corrupting files.
|
|
6
|
+
# See: https://github.com/anthropics/claude-code/issues/32963
|
|
7
|
+
#
|
|
8
|
+
# TRIGGER: PreToolUse
|
|
9
|
+
# MATCHER: Bash,Edit,Write
|
|
10
|
+
#
|
|
11
|
+
# As tool call count grows, the hook progressively tightens:
|
|
12
|
+
# 0-200: Normal (no action)
|
|
13
|
+
# 200-500: Warn every 50 calls about drift risk
|
|
14
|
+
# 500+: Block destructive commands (rm, git push, git reset)
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# {
|
|
18
|
+
# "hooks": {
|
|
19
|
+
# "PreToolUse": [{
|
|
20
|
+
# "matcher": "Bash,Edit,Write",
|
|
21
|
+
# "hooks": [{
|
|
22
|
+
# "type": "command",
|
|
23
|
+
# "command": "~/.claude/hooks/session-drift-guard.sh"
|
|
24
|
+
# }]
|
|
25
|
+
# }]
|
|
26
|
+
# }
|
|
27
|
+
# }
|
|
28
|
+
#
|
|
29
|
+
# Config: CC_DRIFT_WARN=200 CC_DRIFT_BLOCK=500
|
|
30
|
+
|
|
31
|
+
INPUT=$(cat)
|
|
32
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
33
|
+
|
|
34
|
+
WARN_THRESHOLD=${CC_DRIFT_WARN:-200}
|
|
35
|
+
BLOCK_THRESHOLD=${CC_DRIFT_BLOCK:-500}
|
|
36
|
+
COUNTER="/tmp/cc-drift-counter-$(whoami)"
|
|
37
|
+
|
|
38
|
+
# Increment counter
|
|
39
|
+
COUNT=1
|
|
40
|
+
if [ -f "$COUNTER" ]; then
|
|
41
|
+
COUNT=$(( $(cat "$COUNTER") + 1 ))
|
|
42
|
+
fi
|
|
43
|
+
echo "$COUNT" > "$COUNTER"
|
|
44
|
+
|
|
45
|
+
# Phase 1: Normal (no action)
|
|
46
|
+
if [ "$COUNT" -lt "$WARN_THRESHOLD" ]; then
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Phase 2: Warn periodically
|
|
51
|
+
if [ "$COUNT" -lt "$BLOCK_THRESHOLD" ]; then
|
|
52
|
+
if [ $(( COUNT % 50 )) -eq 0 ]; then
|
|
53
|
+
echo "⚠ Session drift warning: $COUNT tool calls" >&2
|
|
54
|
+
echo " Long sessions degrade AI judgment. Consider /compact or restarting." >&2
|
|
55
|
+
fi
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Phase 3: Block destructive commands
|
|
60
|
+
[ -z "$COMMAND" ] && exit 0
|
|
61
|
+
if echo "$COMMAND" | grep -qE '^\s*(sudo\s+)?(rm|git\s+push|git\s+reset|git\s+clean|git\s+checkout\s+--)\b'; then
|
|
62
|
+
echo "BLOCKED: Destructive command after $COUNT tool calls (drift risk)" >&2
|
|
63
|
+
echo "Session has exceeded $BLOCK_THRESHOLD tool calls." >&2
|
|
64
|
+
echo "Restart the session or use /compact before destructive operations." >&2
|
|
65
|
+
exit 2
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Non-destructive commands still pass with warning
|
|
69
|
+
if [ $(( COUNT % 100 )) -eq 0 ]; then
|
|
70
|
+
echo "⚠ High drift risk: $COUNT tool calls. Consider restarting." >&2
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
exit 0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# strip-coauthored-by.sh — Remove or warn about Co-Authored-By trailers
|
|
3
|
+
#
|
|
4
|
+
# Solves: Claude auto-appending Co-Authored-By to every commit without
|
|
5
|
+
# user consent. 489 commits branded without the user wanting it.
|
|
6
|
+
# See: https://github.com/anthropics/claude-code/issues/29999
|
|
7
|
+
#
|
|
8
|
+
# TRIGGER: PreToolUse
|
|
9
|
+
# MATCHER: Bash
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# {
|
|
13
|
+
# "hooks": {
|
|
14
|
+
# "PreToolUse": [{
|
|
15
|
+
# "matcher": "Bash",
|
|
16
|
+
# "hooks": [{
|
|
17
|
+
# "type": "command",
|
|
18
|
+
# "if": "Bash(git commit *)",
|
|
19
|
+
# "command": "~/.claude/hooks/strip-coauthored-by.sh"
|
|
20
|
+
# }]
|
|
21
|
+
# }]
|
|
22
|
+
# }
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# Config: CC_ALLOW_COAUTHOR=1 to allow (default: 0 = block/warn)
|
|
26
|
+
|
|
27
|
+
INPUT=$(cat)
|
|
28
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
29
|
+
[ -z "$COMMAND" ] && exit 0
|
|
30
|
+
|
|
31
|
+
# Only check git commit commands
|
|
32
|
+
echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
|
|
33
|
+
|
|
34
|
+
# Check if Co-Authored-By is in the commit message
|
|
35
|
+
if echo "$COMMAND" | grep -qiE 'Co-Authored-By.*Claude\|Co-Authored-By.*Anthropic\|Co-Authored-By.*noreply@anthropic'; then
|
|
36
|
+
if [ "${CC_ALLOW_COAUTHOR:-0}" = "1" ]; then
|
|
37
|
+
exit 0 # User explicitly allows
|
|
38
|
+
fi
|
|
39
|
+
echo "⚠ Co-Authored-By trailer detected in commit message" >&2
|
|
40
|
+
echo " Set CC_ALLOW_COAUTHOR=1 to allow, or remove the trailer." >&2
|
|
41
|
+
echo " See: https://github.com/anthropics/claude-code/issues/29999" >&2
|
|
42
|
+
# Warn but don't block — user can decide
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
exit 0
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# variable-expansion-guard.sh — Block destructive commands with shell variable expansion
|
|
3
|
+
#
|
|
4
|
+
# Solves: Claude running rm -rf "${LOCALAPPDATA}/" where Bash expands the
|
|
5
|
+
# variable to a real system path, deleting 50+ app folders.
|
|
6
|
+
# See: https://github.com/anthropics/claude-code/issues/39460
|
|
7
|
+
#
|
|
8
|
+
# TRIGGER: PreToolUse
|
|
9
|
+
# MATCHER: Bash
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# {
|
|
13
|
+
# "hooks": {
|
|
14
|
+
# "PreToolUse": [{
|
|
15
|
+
# "matcher": "Bash",
|
|
16
|
+
# "hooks": [{
|
|
17
|
+
# "type": "command",
|
|
18
|
+
# "if": "Bash(rm *)",
|
|
19
|
+
# "command": "~/.claude/hooks/variable-expansion-guard.sh"
|
|
20
|
+
# }]
|
|
21
|
+
# }]
|
|
22
|
+
# }
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# The "if" field (v2.1.85+) limits this to rm commands only.
|
|
26
|
+
|
|
27
|
+
INPUT=$(cat)
|
|
28
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
29
|
+
[ -z "$COMMAND" ] && exit 0
|
|
30
|
+
|
|
31
|
+
# Only check destructive commands
|
|
32
|
+
echo "$COMMAND" | grep -qE '^\s*(sudo\s+)?(rm|mv|cp|chmod|chown)\b' || exit 0
|
|
33
|
+
|
|
34
|
+
# Detect shell variable patterns in arguments
|
|
35
|
+
# $VAR, ${VAR}, $(command), `command`
|
|
36
|
+
if echo "$COMMAND" | grep -qE '\$\{[A-Z_]+\}|\$[A-Z_]{2,}'; then
|
|
37
|
+
VAR=$(echo "$COMMAND" | grep -oE '\$\{[A-Z_]+\}|\$[A-Z_]{2,}' | head -1)
|
|
38
|
+
echo "BLOCKED: Destructive command uses shell variable $VAR" >&2
|
|
39
|
+
echo "Variables may expand to system paths (e.g., \$LOCALAPPDATA → C:/Users/.../AppData/Local)" >&2
|
|
40
|
+
echo "Use explicit paths instead of variables in destructive commands." >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Detect command substitution in rm arguments
|
|
45
|
+
if echo "$COMMAND" | grep -qE '^\s*(sudo\s+)?rm\b.*(\$\(|`)'; then
|
|
46
|
+
echo "BLOCKED: rm with command substitution detected" >&2
|
|
47
|
+
echo "The substituted path could expand to a system directory." >&2
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -405,6 +405,12 @@ function examples() {
|
|
|
405
405
|
'pip-venv-guard.sh': 'Warn on pip install outside venv',
|
|
406
406
|
'no-git-amend-push.sh': 'Warn on amending pushed commits',
|
|
407
407
|
'typosquat-guard.sh': 'Detect npm/pip typosquatting attacks',
|
|
408
|
+
'variable-expansion-guard.sh': 'Block rm/mv with unexpanded shell variables (prevents system path deletion)',
|
|
409
|
+
'bash-trace-guard.sh': 'Block bash -x debug tracing that exposes secrets',
|
|
410
|
+
'strip-coauthored-by.sh': 'Warn on Co-Authored-By trailers in commit messages',
|
|
411
|
+
'session-drift-guard.sh': 'Progressive safety as session ages (warn at 200, block at 500 calls)',
|
|
412
|
+
'post-compact-safety.sh': 'Block irreversible commands after context compaction',
|
|
413
|
+
'read-budget-guard.sh': 'Limit excessive file reading to prevent token waste',
|
|
408
414
|
'hook-permission-fixer.sh': 'Auto-fix missing execute permissions on hooks (SessionStart)',
|
|
409
415
|
'response-budget-guard.sh': 'Track and limit tool calls per response (anti-loop)',
|
|
410
416
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "29.6.
|
|
4
|
-
"description": "One command to make Claude Code safe.
|
|
3
|
+
"version": "29.6.2",
|
|
4
|
+
"description": "One command to make Claude Code safe. 412 example hooks + 8 built-in. 52 CLI commands. 5601 tests. Works with Auto Mode.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc-safe-setup": "index.mjs"
|