cc-safe-setup 5.1.0 → 5.2.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/README.md CHANGED
@@ -224,6 +224,9 @@ Or browse all available examples in [`examples/`](examples/):
224
224
  - **session-handoff.sh** — Auto-save git state and session info to `~/.claude/session-handoff.md` on session end
225
225
  - **diff-size-guard.sh** — Warn/block when committing too many files at once (default: warn at 10, block at 50)
226
226
  - **dependency-audit.sh** — Warn when installing packages not in manifest (npm/pip/cargo supply chain awareness)
227
+ - **symlink-guard.sh** — Detect symlink/junction traversal in rm targets ([#36339](https://github.com/anthropics/claude-code/issues/36339) [#764](https://github.com/anthropics/claude-code/issues/764))
228
+ - **binary-file-guard.sh** — Warn when Write targets binary file types (images, archives)
229
+ - **stale-branch-guard.sh** — Warn when working branch is far behind default
227
230
  - **cost-tracker.sh** — Estimate session token cost and warn at thresholds ($1, $5)
228
231
  - **read-before-edit.sh** — Warn when editing files not recently read (prevents old_string mismatches)
229
232
 
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # binary-file-guard.sh — Warn when Write creates binary/large files
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes tries to Write binary content (images,
7
+ # archives, compiled files) which produces corrupted output.
8
+ # This hook detects binary patterns in Write content.
9
+ #
10
+ # TRIGGER: PreToolUse
11
+ # MATCHER: "Write"
12
+ # ================================================================
13
+
14
+ INPUT=$(cat)
15
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
16
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty' 2>/dev/null)
17
+
18
+ if [[ -z "$FILE" ]]; then
19
+ exit 0
20
+ fi
21
+
22
+ # Check file extension for binary types
23
+ EXT="${FILE##*.}"
24
+ BINARY_EXTS="png|jpg|jpeg|gif|bmp|ico|webp|svg|mp3|mp4|wav|zip|tar|gz|rar|7z|exe|dll|so|dylib|class|pyc|wasm|pdf|doc|docx|xls|xlsx|ppt|pptx"
25
+
26
+ if echo "$EXT" | grep -qiE "^($BINARY_EXTS)$"; then
27
+ echo "WARNING: Writing to binary file type: $FILE" >&2
28
+ echo "Claude Code cannot reliably create binary files." >&2
29
+ echo "Use a proper tool (ImageMagick, ffmpeg, etc.) instead." >&2
30
+ fi
31
+
32
+ exit 0
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # stale-branch-guard.sh — Warn when working on a stale branch
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code can work on a branch that's far behind main,
7
+ # creating merge conflicts. This hook warns when the current
8
+ # branch is 50+ commits behind the default branch.
9
+ #
10
+ # TRIGGER: PostToolUse
11
+ # MATCHER: ""
12
+ #
13
+ # Only checks every 20 tool calls to avoid overhead.
14
+ # ================================================================
15
+
16
+ COUNTER_FILE="/tmp/cc-stale-branch-check"
17
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
18
+ COUNT=$((COUNT + 1))
19
+ echo "$COUNT" > "$COUNTER_FILE"
20
+
21
+ # Only check every 20 tool calls
22
+ [ $((COUNT % 20)) -ne 0 ] && exit 0
23
+
24
+ # Only check in git repos
25
+ [ -d .git ] || exit 0
26
+
27
+ # Get default branch
28
+ DEFAULT=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
29
+ [ -z "$DEFAULT" ] && DEFAULT="main"
30
+
31
+ CURRENT=$(git branch --show-current 2>/dev/null)
32
+ [ -z "$CURRENT" ] || [ "$CURRENT" = "$DEFAULT" ] && exit 0
33
+
34
+ # Count commits behind
35
+ BEHIND=$(git rev-list --count HEAD..origin/"$DEFAULT" 2>/dev/null || echo 0)
36
+
37
+ if [ "$BEHIND" -ge 50 ]; then
38
+ echo "WARNING: Branch '$CURRENT' is $BEHIND commits behind $DEFAULT." >&2
39
+ echo "Consider rebasing: git rebase origin/$DEFAULT" >&2
40
+ elif [ "$BEHIND" -ge 20 ]; then
41
+ echo "NOTE: Branch '$CURRENT' is $BEHIND commits behind $DEFAULT." >&2
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,78 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # symlink-guard.sh — Detect symlink/junction traversal before rm
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # rm -rf on a directory containing symlinks can follow them and
7
+ # delete data outside the target. NTFS junctions on WSL2 are
8
+ # especially dangerous (#36339: entire C:\Users deleted).
9
+ #
10
+ # This hook checks if the rm target contains symlinks pointing
11
+ # outside the current project.
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Bash"
15
+ #
16
+ # WHAT IT BLOCKS:
17
+ # - rm -rf on directories containing symlinks to outside paths
18
+ # - rm on targets that are themselves symlinks to sensitive dirs
19
+ #
20
+ # GitHub Issues: #36339 (93r), #764 (63r), #24964 (135r)
21
+ # ================================================================
22
+
23
+ INPUT=$(cat)
24
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
25
+
26
+ if [[ -z "$COMMAND" ]]; then
27
+ exit 0
28
+ fi
29
+
30
+ # Only check rm commands with recursive flags
31
+ if ! echo "$COMMAND" | grep -qE '^\s*rm\s+.*-[rf]'; then
32
+ exit 0
33
+ fi
34
+
35
+ # Extract target path
36
+ TARGET=$(echo "$COMMAND" | grep -oP 'rm\s+(-[rf]+\s+)*\K\S+' | tail -1)
37
+
38
+ if [[ -z "$TARGET" ]] || [[ ! -e "$TARGET" ]]; then
39
+ exit 0
40
+ fi
41
+
42
+ # Check 1: Is the target itself a symlink?
43
+ if [ -L "$TARGET" ]; then
44
+ REAL=$(readlink -f "$TARGET" 2>/dev/null)
45
+ echo "WARNING: rm target is a symlink." >&2
46
+ echo " Target: $TARGET" >&2
47
+ echo " Points to: $REAL" >&2
48
+ echo " rm -rf will follow and delete the real path." >&2
49
+ # Don't block, just warn — user may intend to delete the link
50
+ fi
51
+
52
+ # Check 2: Does the target directory contain symlinks to outside?
53
+ if [ -d "$TARGET" ]; then
54
+ PROJECT_DIR=$(pwd)
55
+ DANGEROUS_LINKS=$(find "$TARGET" -maxdepth 3 -type l 2>/dev/null | while read link; do
56
+ REAL=$(readlink -f "$link" 2>/dev/null)
57
+ # Check if symlink points outside the project
58
+ if [[ -n "$REAL" ]] && [[ "$REAL" != "$PROJECT_DIR"* ]]; then
59
+ echo "$link -> $REAL"
60
+ fi
61
+ done | head -3)
62
+
63
+ if [[ -n "$DANGEROUS_LINKS" ]]; then
64
+ echo "BLOCKED: rm target contains symlinks pointing outside project." >&2
65
+ echo "" >&2
66
+ echo "Command: $COMMAND" >&2
67
+ echo "Dangerous links:" >&2
68
+ echo "$DANGEROUS_LINKS" | while read line; do
69
+ echo " $line" >&2
70
+ done
71
+ echo "" >&2
72
+ echo "rm -rf would follow these links and delete external data." >&2
73
+ echo "Remove the symlinks first, then delete the directory." >&2
74
+ exit 2
75
+ fi
76
+ fi
77
+
78
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "One command to make Claude Code safe for autonomous operation. 8 built-in + 39 examples. 23 commands including dashboard, issues, create, audit, lint, diff. 260 tests. 2,500+ daily npm downloads.",
5
5
  "main": "index.mjs",
6
6
  "bin": {