cc-safe-setup 29.6.27 → 29.6.28
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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/cc-safe-setup)
|
|
5
5
|
[](https://github.com/yurukusa/cc-safe-setup/actions/workflows/test.yml)
|
|
6
6
|
|
|
7
|
-
**One command to make Claude Code safe for autonomous operation.**
|
|
7
|
+
**One command to make Claude Code safe for autonomous operation.** 511 example hooks · 7,349 tests · 1,000+ installs/day · [日本語](docs/README.ja.md)
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx cc-safe-setup
|
|
@@ -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 511 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,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# credential-file-cat-guard.sh — Block cat/read of package manager credential files
|
|
3
|
+
#
|
|
4
|
+
# Solves: Agent displays full credential files in conversation
|
|
5
|
+
# (#34819 — cat ~/.netrc, ~/.npmrc, ~/.cargo/credentials.toml displayed all tokens)
|
|
6
|
+
#
|
|
7
|
+
# Complements credential-exfil-guard.sh which blocks hunting patterns.
|
|
8
|
+
# This hook blocks direct read of known credential files that the
|
|
9
|
+
# exfil guard misses: .netrc, .npmrc, .cargo/credentials, .docker/config.json, etc.
|
|
10
|
+
#
|
|
11
|
+
# Usage: Add to settings.json as a PreToolUse hook
|
|
12
|
+
#
|
|
13
|
+
# {
|
|
14
|
+
# "hooks": {
|
|
15
|
+
# "PreToolUse": [{
|
|
16
|
+
# "matcher": "Bash",
|
|
17
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/credential-file-cat-guard.sh" }]
|
|
18
|
+
# }]
|
|
19
|
+
# }
|
|
20
|
+
# }
|
|
21
|
+
|
|
22
|
+
INPUT=$(cat)
|
|
23
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
24
|
+
|
|
25
|
+
[ -z "$COMMAND" ] && exit 0
|
|
26
|
+
|
|
27
|
+
# Known credential files that contain tokens/passwords
|
|
28
|
+
CRED_FILES='\.netrc|\.npmrc|\.yarnrc\.yml|\.cargo/credentials|\.docker/config\.json|\.kube/config|\.config/gh/hosts\.yml|\.nuget/NuGet\.Config|\.m2/settings\.xml|\.gradle/gradle\.properties|\.pypirc|\.gem/credentials|\.config/pip/pip\.conf|\.bowerrc|\.composer/auth\.json'
|
|
29
|
+
|
|
30
|
+
# Block cat/head/tail/less/more/grep reading credential files
|
|
31
|
+
if echo "$COMMAND" | grep -qE "(cat|head|tail|less|more|bat)\s+[^\|;]*($CRED_FILES)"; then
|
|
32
|
+
FILE=$(echo "$COMMAND" | grep -oE "[~\/][^\s;|]*($CRED_FILES)[^\s;|]*" | head -1)
|
|
33
|
+
echo "BLOCKED: Reading credential file: $FILE" >&2
|
|
34
|
+
echo " These files contain authentication tokens. Use environment variables instead." >&2
|
|
35
|
+
exit 2
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Block grep searching inside credential files
|
|
39
|
+
if echo "$COMMAND" | grep -qE "grep\s+.*\s+[^\|;]*($CRED_FILES)"; then
|
|
40
|
+
echo "BLOCKED: Searching inside credential file" >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
exit 0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# edit-retry-loop-guard.sh — Detect Edit tool stuck retrying the same file
|
|
3
|
+
#
|
|
4
|
+
# Solves: Edit tool path contamination in long sessions
|
|
5
|
+
# (#35576 — Edit gets "stuck" targeting wrong file, retrying 15+ times)
|
|
6
|
+
#
|
|
7
|
+
# Tracks consecutive Edit failures on the same file. After 3 failures,
|
|
8
|
+
# warns the model to verify the file path.
|
|
9
|
+
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
12
|
+
|
|
13
|
+
[ "$TOOL" = "Edit" ] || exit 0
|
|
14
|
+
|
|
15
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
16
|
+
EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_output.exit_code // empty' 2>/dev/null)
|
|
17
|
+
OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // empty' 2>/dev/null)
|
|
18
|
+
|
|
19
|
+
[ -z "$FILE" ] && exit 0
|
|
20
|
+
|
|
21
|
+
STATE_FILE="/tmp/.cc-edit-retry-$(echo "$FILE" | md5sum | cut -c1-8)"
|
|
22
|
+
|
|
23
|
+
# Check if edit failed (non-zero exit or "no changes" in output)
|
|
24
|
+
if [ "$EXIT_CODE" != "0" ] || echo "$OUTPUT" | grep -qiE 'no changes|not found|old_string.*not.*found'; then
|
|
25
|
+
COUNT=1
|
|
26
|
+
[ -f "$STATE_FILE" ] && COUNT=$(( $(cat "$STATE_FILE") + 1 ))
|
|
27
|
+
echo "$COUNT" > "$STATE_FILE"
|
|
28
|
+
|
|
29
|
+
if [ "$COUNT" -ge 3 ]; then
|
|
30
|
+
echo "WARNING: Edit has failed $COUNT times on: $FILE" >&2
|
|
31
|
+
echo " Verify the file path is correct. Use Read to check the current content." >&2
|
|
32
|
+
echo " The file may have been moved, renamed, or the content changed." >&2
|
|
33
|
+
rm -f "$STATE_FILE"
|
|
34
|
+
fi
|
|
35
|
+
else
|
|
36
|
+
# Success — reset counter
|
|
37
|
+
rm -f "$STATE_FILE"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# push-requires-test-pass-record.sh — Record when tests pass (companion to push-requires-test-pass.sh)
|
|
3
|
+
#
|
|
4
|
+
# PostToolUse hook that detects successful test runs and records the timestamp.
|
|
5
|
+
# The PreToolUse companion then checks this record before allowing git push.
|
|
6
|
+
#
|
|
7
|
+
# Detected test commands: npm test, pytest, cargo test, go test, make test, etc.
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
11
|
+
EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_output.exit_code // .exit_code // empty' 2>/dev/null)
|
|
12
|
+
|
|
13
|
+
[ -z "$COMMAND" ] && exit 0
|
|
14
|
+
|
|
15
|
+
# Only record on exit code 0 (success)
|
|
16
|
+
[ "$EXIT_CODE" = "0" ] || exit 0
|
|
17
|
+
|
|
18
|
+
# Detect test commands
|
|
19
|
+
if echo "$COMMAND" | grep -qE '(npm\s+test|npx\s+jest|npx\s+vitest|npx\s+mocha|pytest|python\s+-m\s+pytest|cargo\s+test|go\s+test|make\s+test|gradle\s+test|mvn\s+test|bundle\s+exec\s+rspec|php\s+artisan\s+test|dotnet\s+test|bash\s+test\.sh)'; then
|
|
20
|
+
STATE_FILE="/tmp/.cc-test-pass-$(pwd | md5sum | cut -c1-8)"
|
|
21
|
+
date +%s > "$STATE_FILE"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# push-requires-test-pass.sh — Block git push to main/production without test verification
|
|
3
|
+
#
|
|
4
|
+
# Solves: Agent pushes broken code to production without running tests
|
|
5
|
+
# (#36673 — pushed broken code 4 times, crashed live SaaS application)
|
|
6
|
+
#
|
|
7
|
+
# How it works:
|
|
8
|
+
# 1. PostToolUse companion records when tests pass (creates state file)
|
|
9
|
+
# 2. This PreToolUse hook blocks git push to protected branches unless tests passed
|
|
10
|
+
#
|
|
11
|
+
# Requires companion hook: push-requires-test-pass-record.sh (PostToolUse)
|
|
12
|
+
#
|
|
13
|
+
# Usage: Add BOTH hooks to settings.json
|
|
14
|
+
#
|
|
15
|
+
# {
|
|
16
|
+
# "hooks": {
|
|
17
|
+
# "PreToolUse": [{
|
|
18
|
+
# "matcher": "Bash",
|
|
19
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/push-requires-test-pass.sh" }]
|
|
20
|
+
# }],
|
|
21
|
+
# "PostToolUse": [{
|
|
22
|
+
# "matcher": "Bash",
|
|
23
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/push-requires-test-pass-record.sh" }]
|
|
24
|
+
# }]
|
|
25
|
+
# }
|
|
26
|
+
# }
|
|
27
|
+
|
|
28
|
+
INPUT=$(cat)
|
|
29
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
30
|
+
|
|
31
|
+
[ -z "$COMMAND" ] && exit 0
|
|
32
|
+
|
|
33
|
+
# Only check git push commands
|
|
34
|
+
echo "$COMMAND" | grep -qE '^\s*git\s+push\b' || exit 0
|
|
35
|
+
|
|
36
|
+
# Protected branches
|
|
37
|
+
PROTECTED='main|master|production|prod|release|deploy'
|
|
38
|
+
|
|
39
|
+
# Check if pushing to a protected branch
|
|
40
|
+
if echo "$COMMAND" | grep -qE "git\s+push\s+\S+\s+($PROTECTED)\b|git\s+push\s+($PROTECTED)\b|git\s+push\s*$"; then
|
|
41
|
+
STATE_FILE="/tmp/.cc-test-pass-$(pwd | md5sum | cut -c1-8)"
|
|
42
|
+
|
|
43
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
44
|
+
echo "BLOCKED: git push to protected branch without test verification" >&2
|
|
45
|
+
echo " Run your test suite first. Tests must pass before pushing." >&2
|
|
46
|
+
echo " Protected branches: main, master, production, prod, release, deploy" >&2
|
|
47
|
+
exit 2
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Check if test pass is recent (within last 30 minutes)
|
|
51
|
+
if [ -f "$STATE_FILE" ]; then
|
|
52
|
+
PASS_TIME=$(cat "$STATE_FILE" 2>/dev/null)
|
|
53
|
+
NOW=$(date +%s)
|
|
54
|
+
AGE=$(( NOW - PASS_TIME ))
|
|
55
|
+
if [ "$AGE" -gt 1800 ]; then
|
|
56
|
+
echo "BLOCKED: Test pass record is stale ($(( AGE / 60 )) minutes old)" >&2
|
|
57
|
+
echo " Re-run tests before pushing to protected branch." >&2
|
|
58
|
+
rm -f "$STATE_FILE"
|
|
59
|
+
exit 2
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
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.28",
|
|
4
|
+
"description": "One command to make Claude Code safe. 511 example hooks + 8 built-in. 56 CLI commands. 7349 tests. Works with Auto Mode.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc-safe-setup": "index.mjs"
|