cc-safe-setup 29.6.11 → 29.6.13

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
@@ -117,7 +117,7 @@ Install any of these: `npx cc-safe-setup --install-example <name>`
117
117
  | `--scan [--apply]` | Tech stack detection |
118
118
  | `--export / --import` | Team config sharing |
119
119
  | `--verify` | Test each hook |
120
- | `--install-example <name>` | Install from 413 examples |
120
+ | `--install-example <name>` | Install from 425 examples |
121
121
  | `--examples [filter]` | Browse examples by keyword |
122
122
  | `--full` | All-in-one setup |
123
123
  | `--status` | Check installed hooks |
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # api-key-in-url-guard.sh — Block API keys embedded in URLs
3
+ #
4
+ # Solves: Claude Code embedding API keys directly in curl/wget URLs
5
+ # instead of using headers or environment variables.
6
+ # Keys in URLs appear in shell history, server logs, proxy logs,
7
+ # and error messages — all places where secrets shouldn't be.
8
+ #
9
+ # Detects:
10
+ # curl https://api.example.com?key=abc123
11
+ # curl https://api.example.com?api_key=abc123
12
+ # curl https://api.example.com?token=abc123
13
+ # wget "https://...?secret=..."
14
+ #
15
+ # Does NOT block:
16
+ # curl -H "Authorization: Bearer $TOKEN" https://...
17
+ # curl with env vars: $API_KEY in header
18
+ #
19
+ # TRIGGER: PreToolUse MATCHER: "Bash"
20
+
21
+ INPUT=$(cat)
22
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
23
+
24
+ [ -z "$COMMAND" ] && exit 0
25
+
26
+ # Only check commands that make HTTP requests
27
+ echo "$COMMAND" | grep -qE '\b(curl|wget|http|fetch)\b' || exit 0
28
+
29
+ # Check for API key patterns in URLs
30
+ if echo "$COMMAND" | grep -qiP '[?&](api[_-]?key|token|secret|password|auth|access[_-]?key|client[_-]?secret)=[^$\s&"'\'']{8,}'; then
31
+ echo "BLOCKED: API key detected in URL query parameter." >&2
32
+ echo "" >&2
33
+ echo "Command: $(echo "$COMMAND" | head -1)" >&2
34
+ echo "" >&2
35
+ echo "API keys in URLs appear in:" >&2
36
+ echo " - Shell history (~/.bash_history)" >&2
37
+ echo " - Server access logs" >&2
38
+ echo " - Proxy/CDN logs" >&2
39
+ echo "" >&2
40
+ echo "Use headers instead:" >&2
41
+ echo " curl -H 'Authorization: Bearer \$TOKEN' https://..." >&2
42
+ echo " curl -H 'X-API-Key: \$API_KEY' https://..." >&2
43
+ exit 2
44
+ fi
45
+
46
+ exit 0
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+ # file-edit-backup.sh — Auto-backup files before Edit/Write overwrites them
3
+ #
4
+ # Solves: Claude Code overwrites important files and the changes are hard
5
+ # to reverse. This creates a timestamped backup before each edit,
6
+ # so you can always recover the previous version.
7
+ #
8
+ # Real incidents:
9
+ # #37478 — .bashrc overwritten without permission
10
+ # #32938 — 11h of inference output deleted
11
+ # #36339 — C:\Users directory wiped (NTFS junction traversal)
12
+ #
13
+ # Backups go to ~/.claude/file-backups/ with timestamps.
14
+ # Old backups (>7 days) are auto-cleaned to prevent disk bloat.
15
+ #
16
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
17
+
18
+ INPUT=$(cat)
19
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
20
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
21
+
22
+ [ -z "$FILE" ] && exit 0
23
+ [ ! -f "$FILE" ] && exit 0 # New file, nothing to backup
24
+
25
+ BACKUP_DIR="$HOME/.claude/file-backups"
26
+ mkdir -p "$BACKUP_DIR"
27
+
28
+ # Create backup with timestamp
29
+ TIMESTAMP=$(date +%Y%m%d-%H%M%S)
30
+ SAFE_NAME=$(echo "$FILE" | tr '/' '_' | sed 's/^_//')
31
+ BACKUP_PATH="${BACKUP_DIR}/${SAFE_NAME}.${TIMESTAMP}"
32
+
33
+ cp "$FILE" "$BACKUP_PATH" 2>/dev/null
34
+
35
+ # Clean old backups (>7 days)
36
+ find "$BACKUP_DIR" -type f -mtime +7 -delete 2>/dev/null
37
+
38
+ exit 0
@@ -0,0 +1,57 @@
1
+ #!/bin/bash
2
+ # gh-cli-destructive-guard.sh — Block destructive GitHub CLI operations
3
+ #
4
+ # Solves: Claude Code running dangerous gh commands without confirmation:
5
+ # - Closing/deleting issues or PRs
6
+ # - Deleting repos, releases, or branches
7
+ # - Merging PRs without review
8
+ # - Modifying repo settings
9
+ #
10
+ # The gh CLI is powerful but destructive operations should require
11
+ # explicit human approval, not AI autonomy.
12
+ #
13
+ # TRIGGER: PreToolUse MATCHER: "Bash"
14
+
15
+ INPUT=$(cat)
16
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
17
+
18
+ [ -z "$COMMAND" ] && exit 0
19
+
20
+ # Only check gh commands
21
+ echo "$COMMAND" | grep -qE '\bgh\s' || exit 0
22
+
23
+ # Block destructive issue operations
24
+ if echo "$COMMAND" | grep -qE 'gh\s+issue\s+(close|delete|lock|transfer)'; then
25
+ echo "BLOCKED: Destructive GitHub Issue operation." >&2
26
+ echo "Command: $COMMAND" >&2
27
+ exit 2
28
+ fi
29
+
30
+ # Block destructive PR operations
31
+ if echo "$COMMAND" | grep -qE 'gh\s+pr\s+(close|merge|ready)'; then
32
+ echo "BLOCKED: Destructive GitHub PR operation." >&2
33
+ echo " gh pr merge/close requires human review." >&2
34
+ echo "Command: $COMMAND" >&2
35
+ exit 2
36
+ fi
37
+
38
+ # Block repo deletion
39
+ if echo "$COMMAND" | grep -qE 'gh\s+repo\s+delete'; then
40
+ echo "BLOCKED: Repository deletion." >&2
41
+ exit 2
42
+ fi
43
+
44
+ # Block release deletion
45
+ if echo "$COMMAND" | grep -qE 'gh\s+release\s+delete'; then
46
+ echo "BLOCKED: Release deletion." >&2
47
+ exit 2
48
+ fi
49
+
50
+ # Block branch deletion via gh
51
+ if echo "$COMMAND" | grep -qE 'gh\s+api\s+.*DELETE'; then
52
+ echo "BLOCKED: Destructive GitHub API call (DELETE method)." >&2
53
+ echo "Command: $COMMAND" >&2
54
+ exit 2
55
+ fi
56
+
57
+ exit 0
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+ # git-checkout-uncommitted-guard.sh — Block branch switching with uncommitted changes
3
+ #
4
+ # Solves: Claude Code switching branches while uncommitted changes exist,
5
+ # causing silent data loss when the target branch overwrites modified files.
6
+ # Real incident: #39394 — hours of work lost when Claude decided to
7
+ # organize commits on a different branch.
8
+ #
9
+ # Why this matters: git checkout <branch> silently overwrites modified files
10
+ # if the target branch has different versions. Unlike "git checkout -- .",
11
+ # the user doesn't intend to discard changes — they just wanted to switch
12
+ # branches. The data loss is a side effect, not the goal.
13
+ #
14
+ # Detects:
15
+ # git checkout <branch> (when uncommitted changes exist)
16
+ # git switch <branch> (modern equivalent)
17
+ #
18
+ # Does NOT block:
19
+ # git checkout -b <branch> (creating new branch — preserves changes)
20
+ # git switch -c <branch> (creating new branch — preserves changes)
21
+ # git checkout -- <files> (handled by uncommitted-discard-guard)
22
+ # Any checkout when working tree is clean
23
+ #
24
+ # TRIGGER: PreToolUse MATCHER: "Bash"
25
+
26
+ INPUT=$(cat)
27
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
28
+
29
+ [ -z "$COMMAND" ] && exit 0
30
+
31
+ # Only check git checkout/switch commands (not -b/-c which create branches)
32
+ IS_CHECKOUT=false
33
+ if echo "$COMMAND" | grep -qE 'git\s+checkout\s+[^-]' && ! echo "$COMMAND" | grep -qE 'git\s+checkout\s+(-b|-B)\s'; then
34
+ # Exclude "git checkout -- <files>" (handled elsewhere)
35
+ echo "$COMMAND" | grep -qE 'git\s+checkout\s+--\s' && exit 0
36
+ IS_CHECKOUT=true
37
+ fi
38
+ if echo "$COMMAND" | grep -qE 'git\s+switch\s+[^-]' && ! echo "$COMMAND" | grep -qE 'git\s+switch\s+(-c|-C)\s'; then
39
+ IS_CHECKOUT=true
40
+ fi
41
+
42
+ [ "$IS_CHECKOUT" = "false" ] && exit 0
43
+
44
+ # Check for uncommitted changes
45
+ DIRTY=$(git status --porcelain 2>/dev/null | head -1)
46
+ if [ -n "$DIRTY" ]; then
47
+ CHANGED=$(git status --porcelain 2>/dev/null | wc -l)
48
+ echo "BLOCKED: Cannot switch branches with $CHANGED uncommitted change(s)." >&2
49
+ echo "" >&2
50
+ echo "Command: $COMMAND" >&2
51
+ echo "" >&2
52
+ echo "Uncommitted changes would be overwritten by the target branch." >&2
53
+ echo "Options:" >&2
54
+ echo " git stash # save changes, switch, then git stash pop" >&2
55
+ echo " git commit -m 'WIP' # commit changes before switching" >&2
56
+ echo " git checkout -b <new-branch> # create branch (keeps changes)" >&2
57
+ exit 2
58
+ fi
59
+
60
+ exit 0
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ # plan-mode-edit-guard.sh — Warn when editing non-plan files during plan mode
3
+ #
4
+ # Solves: Claude editing source files while plan mode is active,
5
+ # bypassing the "plan only" constraint. Real incident: #38255 —
6
+ # Opus edited api/app/routers/ despite system reminders saying
7
+ # plan mode was active.
8
+ #
9
+ # How it works: Uses a flag file to track plan mode state.
10
+ # - Create ~/.claude/plan-mode-active to enable (touch the file)
11
+ # - Delete it to disable (rm the file)
12
+ # - When active, Edit/Write to non-plan files triggers a warning
13
+ #
14
+ # Integrate with your workflow:
15
+ # Before entering plan mode: touch ~/.claude/plan-mode-active
16
+ # After exiting plan mode: rm -f ~/.claude/plan-mode-active
17
+ #
18
+ # Does NOT block (warns only) because false positives during
19
+ # legitimate plan-mode file creation would be disruptive.
20
+ # Change exit 0 to exit 2 at the bottom to enforce strict blocking.
21
+ #
22
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
23
+
24
+ INPUT=$(cat)
25
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
26
+
27
+ FLAG="$HOME/.claude/plan-mode-active"
28
+
29
+ # Only check when plan mode flag exists
30
+ [ ! -f "$FLAG" ] && exit 0
31
+
32
+ # Get the file being edited
33
+ case "$TOOL" in
34
+ Edit)
35
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
36
+ ;;
37
+ Write)
38
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
39
+ ;;
40
+ *)
41
+ exit 0
42
+ ;;
43
+ esac
44
+
45
+ [ -z "$FILE" ] && exit 0
46
+
47
+ # Allow plan files
48
+ if echo "$FILE" | grep -qiE '\.plan\.md$|plan\.md$|/plans/|task_plan\.md|findings\.md|progress\.md'; then
49
+ exit 0
50
+ fi
51
+
52
+ # Allow CLAUDE.md and memory files (often updated during planning)
53
+ if echo "$FILE" | grep -qiE 'CLAUDE\.md$|MEMORY\.md$|memory/|\.claude/'; then
54
+ exit 0
55
+ fi
56
+
57
+ # Warn about non-plan file edits
58
+ echo "⚠ PLAN MODE ACTIVE: Editing non-plan file: $FILE" >&2
59
+ echo " Plan mode should only modify plan files." >&2
60
+ echo " Remove ~/.claude/plan-mode-active to disable this check." >&2
61
+
62
+ # Warning only (exit 0). Change to exit 2 to block.
63
+ exit 0
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # unicode-corruption-check.sh — Detect Unicode corruption after Edit/Write
3
+ #
4
+ # Solves: Claude Code's Edit tool corrupting non-ASCII Unicode characters
5
+ # (typographic quotes, em dashes, accented characters) in string literals.
6
+ # Real incident: #38765 — Edit tool replaced Unicode quotes with
7
+ # escaped sequences, breaking string comparisons.
8
+ #
9
+ # How it works: After Edit/Write, checks if the file contains common
10
+ # corruption patterns (replacement characters, broken UTF-8 sequences).
11
+ #
12
+ # TRIGGER: PostToolUse MATCHER: "Edit|Write"
13
+
14
+ INPUT=$(cat)
15
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
16
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
17
+
18
+ [ -z "$FILE" ] && exit 0
19
+ [ ! -f "$FILE" ] && exit 0
20
+
21
+ # Skip binary files
22
+ file "$FILE" 2>/dev/null | grep -q "text" || exit 0
23
+
24
+ # Check for Unicode replacement character (U+FFFD) — sign of broken encoding
25
+ if grep -Pq '\xef\xbf\xbd' "$FILE" 2>/dev/null; then
26
+ echo "⚠ Unicode corruption detected in $FILE" >&2
27
+ echo " Found U+FFFD replacement characters (broken encoding)." >&2
28
+ echo " Review the edit — non-ASCII characters may have been corrupted." >&2
29
+ fi
30
+
31
+ # Check for common corruption: escaped Unicode in places it shouldn't be
32
+ # e.g., \u2018 appearing in plain text files (not JSON/JS)
33
+ if ! echo "$FILE" | grep -qE '\.(json|js|ts|jsx|tsx)$'; then
34
+ if grep -qE '\\u[0-9a-fA-F]{4}' "$FILE" 2>/dev/null; then
35
+ echo "⚠ Possible Unicode escape in non-JS file: $FILE" >&2
36
+ echo " Found \\uXXXX sequences that may be corrupted characters." >&2
37
+ fi
38
+ fi
39
+
40
+ exit 0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "29.6.11",
4
- "description": "One command to make Claude Code safe. 421 example hooks + 8 built-in. 52 CLI commands. 5668 tests. Works with Auto Mode.",
3
+ "version": "29.6.13",
4
+ "description": "One command to make Claude Code safe. 427 example hooks + 8 built-in. 52 CLI commands. 5718 tests. Works with Auto Mode.",
5
5
  "main": "index.mjs",
6
6
  "bin": {
7
7
  "cc-safe-setup": "index.mjs"