cc-safe-setup 2.4.0 → 2.5.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.
@@ -0,0 +1,145 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # case-sensitive-guard.sh — Case-Insensitive Filesystem Safety Guard
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Detects case-insensitive filesystems (exFAT, NTFS, HFS+, APFS
7
+ # case-insensitive) and warns before mkdir/rm that would collide
8
+ # due to case folding.
9
+ #
10
+ # Real incident: GitHub #37875 — Claude created "Content" dir on
11
+ # exFAT drive where "content" already existed. Both resolved to
12
+ # the same path. Claude then ran rm -rf on "content", destroying
13
+ # all user data.
14
+ #
15
+ # TRIGGER: PreToolUse
16
+ # MATCHER: "Bash"
17
+ #
18
+ # WHAT IT BLOCKS (exit 2):
19
+ # - rm -rf on case-insensitive FS when a case-variant directory
20
+ # exists that resolves to the same inode
21
+ # - mkdir that would silently collide with existing dir on
22
+ # case-insensitive FS
23
+ #
24
+ # WHAT IT ALLOWS (exit 0):
25
+ # - All commands on case-sensitive filesystems
26
+ # - rm/mkdir where no case collision exists
27
+ # - Commands that don't involve mkdir or rm
28
+ #
29
+ # HOW IT WORKS:
30
+ # 1. Extract target path from mkdir/rm commands
31
+ # 2. Check if filesystem is case-insensitive (create temp file,
32
+ # check if uppercase variant exists)
33
+ # 3. If case-insensitive, check for case-variant collisions
34
+ # 4. Block if collision detected
35
+ # ================================================================
36
+
37
+ INPUT=$(cat)
38
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
39
+
40
+ if [[ -z "$COMMAND" ]]; then
41
+ exit 0
42
+ fi
43
+
44
+ # Only check mkdir and rm commands
45
+ if ! echo "$COMMAND" | grep -qE '^\s*(mkdir|rm)\s'; then
46
+ exit 0
47
+ fi
48
+
49
+ # Extract the target path
50
+ TARGET=""
51
+ if echo "$COMMAND" | grep -qE '^\s*mkdir'; then
52
+ TARGET=$(echo "$COMMAND" | grep -oP 'mkdir\s+(-p\s+)?\K\S+' | tail -1)
53
+ elif echo "$COMMAND" | grep -qE '^\s*rm\s'; then
54
+ TARGET=$(echo "$COMMAND" | grep -oP 'rm\s+(-[rf]+\s+)*\K\S+' | tail -1)
55
+ fi
56
+
57
+ if [[ -z "$TARGET" ]]; then
58
+ exit 0
59
+ fi
60
+
61
+ # Resolve the parent directory
62
+ PARENT_DIR=$(dirname "$TARGET" 2>/dev/null)
63
+ BASE_NAME=$(basename "$TARGET" 2>/dev/null)
64
+
65
+ if [[ -z "$PARENT_DIR" ]] || [[ ! -d "$PARENT_DIR" ]]; then
66
+ exit 0
67
+ fi
68
+
69
+ # --- Check if filesystem is case-insensitive ---
70
+ is_case_insensitive() {
71
+ local dir="$1"
72
+ local test_file="${dir}/.cc_case_test_$$"
73
+ local test_upper="${dir}/.CC_CASE_TEST_$$"
74
+
75
+ # Create lowercase test file
76
+ if ! touch "$test_file" 2>/dev/null; then
77
+ return 1 # Can't test, assume case-sensitive (safe default)
78
+ fi
79
+
80
+ # Check if uppercase variant resolves to the same file
81
+ if [[ -f "$test_upper" ]]; then
82
+ rm -f "$test_file" 2>/dev/null
83
+ return 0 # Case-insensitive
84
+ else
85
+ rm -f "$test_file" 2>/dev/null
86
+ return 1 # Case-sensitive
87
+ fi
88
+ }
89
+
90
+ # --- Check for case-variant collisions ---
91
+ find_case_collision() {
92
+ local dir="$1"
93
+ local name="$2"
94
+ local name_lower
95
+ name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]')
96
+
97
+ # List directory entries and check for case variants
98
+ while IFS= read -r entry; do
99
+ local entry_lower
100
+ entry_lower=$(echo "$entry" | tr '[:upper:]' '[:lower:]')
101
+ if [[ "$entry_lower" == "$name_lower" ]] && [[ "$entry" != "$name" ]]; then
102
+ echo "$entry"
103
+ return 0
104
+ fi
105
+ done < <(ls -1 "$dir" 2>/dev/null)
106
+
107
+ return 1
108
+ }
109
+
110
+ # Only proceed if filesystem is case-insensitive
111
+ if ! is_case_insensitive "$PARENT_DIR"; then
112
+ exit 0
113
+ fi
114
+
115
+ # Check for collision
116
+ COLLISION=$(find_case_collision "$PARENT_DIR" "$BASE_NAME")
117
+
118
+ if [[ -n "$COLLISION" ]]; then
119
+ if echo "$COMMAND" | grep -qE '^\s*rm\s'; then
120
+ echo "BLOCKED: Case-insensitive filesystem collision detected." >&2
121
+ echo "" >&2
122
+ echo "Command: $COMMAND" >&2
123
+ echo "" >&2
124
+ echo "Target: $TARGET" >&2
125
+ echo "Collides with: $PARENT_DIR/$COLLISION" >&2
126
+ echo "" >&2
127
+ echo "This filesystem is case-insensitive (exFAT, NTFS, HFS+, etc.)." >&2
128
+ echo "'$BASE_NAME' and '$COLLISION' resolve to the SAME path." >&2
129
+ echo "rm -rf would destroy the data you think you're keeping." >&2
130
+ echo "" >&2
131
+ echo "Verify with: ls -la \"$PARENT_DIR\" | grep -i \"$BASE_NAME\"" >&2
132
+ exit 2
133
+ elif echo "$COMMAND" | grep -qE '^\s*mkdir'; then
134
+ echo "WARNING: Case-insensitive filesystem — directory already exists." >&2
135
+ echo "" >&2
136
+ echo "Command: $COMMAND" >&2
137
+ echo "Existing: $PARENT_DIR/$COLLISION" >&2
138
+ echo "" >&2
139
+ echo "On this filesystem, '$BASE_NAME' and '$COLLISION' are the same path." >&2
140
+ echo "mkdir will either fail or silently use the existing directory." >&2
141
+ exit 2
142
+ fi
143
+ fi
144
+
145
+ exit 0
package/index.mjs CHANGED
@@ -85,7 +85,11 @@ if (HELP) {
85
85
  npx cc-safe-setup --uninstall Remove all installed hooks
86
86
  npx cc-safe-setup --examples List 25 example hooks (5 categories)
87
87
  npx cc-safe-setup --install-example <name> Install a specific example
88
- npx cc-safe-setup --audit Analyze your setup and recommend missing protections
88
+ npx cc-safe-setup --full Complete setup: hooks + scan + audit + badge
89
+ npx cc-safe-setup --audit Safety score (0-100) with fixes
90
+ npx cc-safe-setup --audit --fix Auto-fix missing protections
91
+ npx cc-safe-setup --scan Detect tech stack, recommend hooks
92
+ npx cc-safe-setup --learn Learn from your block history
89
93
  npx cc-safe-setup --help Show this help
90
94
 
91
95
  Hooks installed:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "2.4.0",
4
- "description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 25 installable examples. Destructive blocker, branch guard, database wipe protection, dotfile guard, and more.",
3
+ "version": "2.5.0",
4
+ "description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 26 installable examples. Destructive blocker, branch guard, database wipe protection, case-insensitive FS guard, and more.",
5
5
  "main": "index.mjs",
6
6
  "bin": {
7
7
  "cc-safe-setup": "index.mjs"