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 +1 -1
- package/examples/api-key-in-url-guard.sh +46 -0
- package/examples/file-edit-backup.sh +38 -0
- package/examples/gh-cli-destructive-guard.sh +57 -0
- package/examples/git-checkout-uncommitted-guard.sh +60 -0
- package/examples/plan-mode-edit-guard.sh +63 -0
- package/examples/unicode-corruption-check.sh +40 -0
- package/package.json +2 -2
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
|
|
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.
|
|
4
|
-
"description": "One command to make Claude Code safe.
|
|
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"
|