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
  [![npm downloads](https://img.shields.io/npm/dw/cc-safe-setup)](https://www.npmjs.com/package/cc-safe-setup)
5
5
  [![tests](https://github.com/yurukusa/cc-safe-setup/actions/workflows/test.yml/badge.svg)](https://github.com/yurukusa/cc-safe-setup/actions/workflows/test.yml)
6
6
 
7
- **One command to make Claude Code safe for autonomous operation.** 507 example hooks · 7,341 tests · 1,000+ installs/day · [日本語](docs/README.ja.md)
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 507 examples |
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.27",
4
- "description": "One command to make Claude Code safe. 507 example hooks + 8 built-in. 56 CLI commands. 7341 tests. Works with Auto Mode.",
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"