cc-safe-setup 29.6.33 → 29.6.36

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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/examples/claudemd-violation-detector.sh +36 -0
  3. package/examples/clear-command-confirm-guard.sh +21 -0
  4. package/examples/core-file-protect-guard.sh +91 -0
  5. package/examples/cwd-drift-detector.sh +47 -0
  6. package/examples/deployment-verify-guard.sh +81 -0
  7. package/examples/edit-old-string-validator.sh +37 -0
  8. package/examples/encoding-preserve-guard.sh +34 -0
  9. package/examples/git-crypt-worktree-guard.sh +36 -0
  10. package/examples/git-operations-require-approval.sh +99 -0
  11. package/examples/line-ending-guard.sh +30 -0
  12. package/examples/permission-pattern-auto-allow.sh +50 -0
  13. package/examples/read-audit-log.sh +34 -0
  14. package/examples/session-duration-guard.sh +51 -0
  15. package/examples/settings-auto-backup.sh +53 -0
  16. package/examples/settings-mutation-detector.sh +45 -0
  17. package/examples/subagent-context-size-guard.sh +26 -0
  18. package/examples/symlink-protect.sh +12 -0
  19. package/examples/temp-file-cleanup-stop.sh +28 -0
  20. package/examples/test-before-commit.sh +13 -16
  21. package/examples/token-spike-alert.sh +51 -0
  22. package/examples/virtual-cwd-helper.sh +40 -0
  23. package/examples/worktree-delete-guard.sh +43 -0
  24. package/examples/worktree-path-validator.sh +42 -0
  25. package/examples/write-shrink-guard.sh +46 -0
  26. package/index.mjs +631 -138
  27. package/package.json +2 -2
  28. package/scripts/generate-categories.mjs +206 -0
  29. package/scripts.json +4 -1
  30. package/test.sh.new_tests +0 -0
  31. package/test.sh.patch +0 -0
  32. package/tests/test-core-file-protect-guard.sh +73 -0
  33. package/tests/test-deployment-verify-guard.sh +74 -0
  34. package/tests/test-git-operations-require-approval.sh +65 -0
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.** 611 example hooks · 10,411 tests · 1,000+ installs/day · [日本語](docs/README.ja.md)
7
+ **One command to make Claude Code safe for autonomous operation.** 634 example hooks · 11,933 tests · 1,000+ installs/day · [日本語](docs/README.ja.md)
8
8
 
9
9
  ```bash
10
10
  npx cc-safe-setup
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ # claudemd-violation-detector.sh — Remind critical CLAUDE.md rules after tool use
3
+ #
4
+ # Solves: Claude ignores CLAUDE.md instructions, especially after
5
+ # context compaction or in long sessions (#40930).
6
+ #
7
+ # How it works: After each tool use, extracts and prints
8
+ # critical rules (ABSOLUTE/MUST NEVER/NEVER/禁止) from CLAUDE.md
9
+ # as a reminder. Runs every N tool calls to avoid noise.
10
+ #
11
+ # TRIGGER: PostToolUse
12
+ # MATCHER: ""
13
+
14
+ set -euo pipefail
15
+
16
+ # Rate limit: only remind every 20 tool calls
17
+ COUNTER_FILE="/tmp/claudemd-reminder-counter"
18
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
19
+ COUNT=$((COUNT + 1))
20
+ echo "$COUNT" > "$COUNTER_FILE"
21
+ [ $((COUNT % 20)) -ne 0 ] && exit 0
22
+
23
+ # Find CLAUDE.md
24
+ CLAUDEMD=""
25
+ for candidate in "CLAUDE.md" ".claude/CLAUDE.md" "../CLAUDE.md"; do
26
+ [ -f "$candidate" ] && CLAUDEMD="$candidate" && break
27
+ done
28
+ [ -z "$CLAUDEMD" ] && exit 0
29
+
30
+ # Extract critical rules
31
+ RULES=$(grep -iE '(ABSOLUTE|MUST NEVER|NEVER DO|禁止|絶対)' "$CLAUDEMD" 2>/dev/null | head -5 || true)
32
+ [ -z "$RULES" ] && exit 0
33
+
34
+ echo "📋 CLAUDE.md critical rules reminder:" >&2
35
+ echo "$RULES" >&2
36
+ exit 0
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ # clear-command-confirm-guard.sh — Block accidental /clear command
3
+ #
4
+ # Solves: /clear destroys all conversation context with zero
5
+ # confirmation. Prefix matching means /c + Enter can
6
+ # accidentally trigger /clear instead of /commit or /compact (#40931).
7
+ #
8
+ # How it works: Blocks /clear entirely. Use /compact to reduce
9
+ # context without losing it.
10
+ #
11
+ # TRIGGER: UserPromptSubmit
12
+ # MATCHER: "^/clear$"
13
+
14
+ INPUT=$(cat)
15
+ PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
16
+
17
+ if echo "$PROMPT" | grep -qE '^/clear$'; then
18
+ echo "BLOCKED: /clear permanently destroys all context. Use /compact instead to reduce context safely." >&2
19
+ exit 2
20
+ fi
21
+ exit 0
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # core-file-protect-guard.sh — Block edits to core/config/rules files
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes makes unprompted architectural changes to
7
+ # game rules, configuration files, and core logic files. This hook
8
+ # blocks modifications to files matching configurable glob patterns.
9
+ #
10
+ # Protects files matching CC_PROTECTED_FILES patterns (colon-separated).
11
+ # Default: "*rules*:*config*:*core*"
12
+ #
13
+ # Catches:
14
+ # - Edit/Write tool targeting protected files
15
+ # - Bash commands using sed -i or awk -i on protected files
16
+ #
17
+ # See: https://github.com/anthropics/claude-code/issues/40788
18
+ #
19
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write|Bash"
20
+ #
21
+ # Configuration:
22
+ # CC_PROTECTED_FILES — colon-separated glob patterns
23
+ # Default: "*rules*:*config*:*core*"
24
+ # ================================================================
25
+
26
+ INPUT=$(cat)
27
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
28
+
29
+ # Configurable protected file patterns (colon-separated globs)
30
+ PROTECTED="${CC_PROTECTED_FILES:-*rules*:*config*:*core*}"
31
+
32
+ # Convert colon-separated globs to a function that checks a filename
33
+ matches_protected() {
34
+ local filepath="$1"
35
+ local basename
36
+ basename=$(basename "$filepath")
37
+
38
+ IFS=':' read -ra PATTERNS <<< "$PROTECTED"
39
+ for pattern in "${PATTERNS[@]}"; do
40
+ # Use bash glob matching (case-insensitive via shopt)
41
+ if [[ "$basename" == $pattern ]] || [[ "$filepath" == *$pattern* ]]; then
42
+ return 0
43
+ fi
44
+ done
45
+ return 1
46
+ }
47
+
48
+ # Handle Edit/Write tools
49
+ if [[ "$TOOL" == "Edit" || "$TOOL" == "Write" ]]; then
50
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
51
+ [ -z "$FILE" ] && exit 0
52
+
53
+ if matches_protected "$FILE"; then
54
+ echo "BLOCKED: Cannot modify protected file: $FILE" >&2
55
+ echo "" >&2
56
+ echo "Protected patterns: $PROTECTED" >&2
57
+ echo "Configure with CC_PROTECTED_FILES env var." >&2
58
+ echo "" >&2
59
+ echo "See: https://github.com/anthropics/claude-code/issues/40788" >&2
60
+ exit 2
61
+ fi
62
+ exit 0
63
+ fi
64
+
65
+ # Handle Bash tool — check for sed -i / awk -i targeting protected files
66
+ if [[ "$TOOL" == "Bash" ]]; then
67
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
68
+ [ -z "$COMMAND" ] && exit 0
69
+
70
+ # Skip echo/printf
71
+ echo "$COMMAND" | grep -qE '^\s*(echo|printf)\s' && exit 0
72
+
73
+ # Check for sed -i or awk -i inplace targeting protected files
74
+ if echo "$COMMAND" | grep -qE '(sed\s+-i|awk\s+-i\s+inplace)'; then
75
+ # Extract potential file arguments from the command
76
+ IFS=':' read -ra PATTERNS <<< "$PROTECTED"
77
+ for pattern in "${PATTERNS[@]}"; do
78
+ if echo "$COMMAND" | grep -qE "$pattern"; then
79
+ echo "BLOCKED: In-place edit targets protected file pattern: $pattern" >&2
80
+ echo "" >&2
81
+ echo "Command: $COMMAND" >&2
82
+ echo "Protected patterns: $PROTECTED" >&2
83
+ echo "" >&2
84
+ echo "See: https://github.com/anthropics/claude-code/issues/40788" >&2
85
+ exit 2
86
+ fi
87
+ done
88
+ fi
89
+ fi
90
+
91
+ exit 0
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # cwd-drift-detector.sh — Warn when destructive commands run outside project root
3
+ #
4
+ # Solves: Claude frequently loses track of which directory
5
+ # it is in, risking destructive commands in the wrong
6
+ # location (#1669). git reset --hard in the wrong
7
+ # directory can destroy unrelated work.
8
+ #
9
+ # How it works: For destructive commands (git reset, rm -rf,
10
+ # git clean, git checkout -- .), checks if the current
11
+ # directory looks like a project root (has .git, package.json,
12
+ # etc). Warns if it doesn't.
13
+ #
14
+ # TRIGGER: PreToolUse
15
+ # MATCHER: "Bash"
16
+
17
+ set -euo pipefail
18
+ INPUT=$(cat)
19
+
20
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
21
+ [ -z "$COMMAND" ] && exit 0
22
+
23
+ # Only check destructive commands
24
+ if ! echo "$COMMAND" | grep -qE '(git\s+(reset|clean|checkout\s+--|push\s+--force)|rm\s+-rf|DROP\s+TABLE|DROP\s+DATABASE)'; then
25
+ exit 0
26
+ fi
27
+
28
+ # Check if we're in a project root
29
+ CWD=$(pwd)
30
+ IS_PROJECT=false
31
+
32
+ for marker in .git package.json Cargo.toml go.mod pyproject.toml Makefile; do
33
+ if [ -e "$CWD/$marker" ]; then
34
+ IS_PROJECT=true
35
+ break
36
+ fi
37
+ done
38
+
39
+ if [ "$IS_PROJECT" = false ]; then
40
+ echo "WARNING: Destructive command detected outside project root." >&2
41
+ echo " CWD: $CWD" >&2
42
+ echo " Command: $(echo "$COMMAND" | head -c 100)" >&2
43
+ echo " No project markers (.git, package.json, etc) found." >&2
44
+ echo " Verify you are in the correct directory." >&2
45
+ fi
46
+
47
+ exit 0
@@ -0,0 +1,81 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # deployment-verify-guard.sh — Warn if committing without post-deploy verification
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes reports "deployment successful" without
7
+ # actually verifying the deployment works. This hook tracks deploy
8
+ # commands and checks that functional verification was performed
9
+ # before the next git commit.
10
+ #
11
+ # How it works:
12
+ # 1. When a deploy command is detected, logs timestamp to a marker file
13
+ # 2. When verification commands (test, curl, log grep) run, clears the marker
14
+ # 3. When git commit is attempted after a deploy without verification,
15
+ # emits a warning (non-blocking, exit 0)
16
+ #
17
+ # See: https://github.com/anthropics/claude-code/issues/40861
18
+ #
19
+ # TRIGGER: PreToolUse MATCHER: "Bash"
20
+ #
21
+ # Configuration:
22
+ # CC_DEPLOY_COMMANDS — regex pattern for deploy commands
23
+ # Default: "systemctl restart|docker restart|docker-compose up|deploy|kubectl apply|terraform apply|heroku push"
24
+ #
25
+ # CC_VERIFY_COMMANDS — regex pattern for verification commands
26
+ # Default: "curl|wget|test |pytest|npm test|jest|mocha|rspec|go test|cargo test|make test|grep.*log|tail.*log|journalctl|docker logs|health"
27
+ # ================================================================
28
+
29
+ INPUT=$(cat)
30
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
31
+
32
+ [ -z "$COMMAND" ] && exit 0
33
+
34
+ MARKER="/tmp/cc-deploy-pending-$$"
35
+
36
+ # Configurable deploy command patterns
37
+ DEPLOY_PATTERN="${CC_DEPLOY_COMMANDS:-systemctl\s+restart|docker\s+restart|docker-compose\s+up|docker\s+compose\s+up|\bdeploy\b|kubectl\s+apply|terraform\s+apply|heroku\s+.*push|fly\s+deploy}"
38
+
39
+ # Configurable verify command patterns
40
+ VERIFY_PATTERN="${CC_VERIFY_COMMANDS:-\bcurl\b|\bwget\b|\btest\s|\bpytest\b|npm\s+test|\bjest\b|\bmocha\b|\brspec\b|go\s+test|cargo\s+test|make\s+test|grep.*log|tail.*log|\bjournalctl\b|docker\s+logs|\bhealth}"
41
+
42
+ # Skip echo/printf
43
+ echo "$COMMAND" | grep -qE '^\s*(echo|printf)\s' && exit 0
44
+
45
+ # Check if this is a deploy command
46
+ if echo "$COMMAND" | grep -qiE "$DEPLOY_PATTERN"; then
47
+ date +%s > "$MARKER"
48
+ echo "Deploy detected. Verification will be required before commit." >&2
49
+ exit 0
50
+ fi
51
+
52
+ # Check if this is a verification command — clear the deploy marker
53
+ if echo "$COMMAND" | grep -qiE "$VERIFY_PATTERN"; then
54
+ if [ -f "$MARKER" ]; then
55
+ rm -f "$MARKER"
56
+ fi
57
+ exit 0
58
+ fi
59
+
60
+ # Check if this is a git commit after an unverified deploy
61
+ if echo "$COMMAND" | grep -qE '\bgit\s+commit\b'; then
62
+ if [ -f "$MARKER" ]; then
63
+ DEPLOY_TIME=$(cat "$MARKER" 2>/dev/null || echo "unknown")
64
+ echo "WARNING: Committing after deployment without verification." >&2
65
+ echo "" >&2
66
+ echo "A deploy command was run (at timestamp $DEPLOY_TIME) but no" >&2
67
+ echo "verification command was detected since then." >&2
68
+ echo "" >&2
69
+ echo "Recommended verifications:" >&2
70
+ echo " curl http://localhost:<port>/health" >&2
71
+ echo " npm test / pytest / go test" >&2
72
+ echo " docker logs <container> | tail" >&2
73
+ echo " journalctl -u <service> --since '5 min ago'" >&2
74
+ echo "" >&2
75
+ echo "See: https://github.com/anthropics/claude-code/issues/40861" >&2
76
+ # Non-blocking — just warn
77
+ rm -f "$MARKER"
78
+ fi
79
+ fi
80
+
81
+ exit 0
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # edit-old-string-validator.sh — Pre-validate Edit tool old_string exists
3
+ #
4
+ # Solves: Parallel Edit tool calls cascade-fail when one Edit's
5
+ # old_string doesn't match the file content (#22264).
6
+ # By catching mismatches before execution, sibling
7
+ # edits in the same batch can proceed normally.
8
+ #
9
+ # How it works: Reads the Edit tool input, checks if old_string
10
+ # exists in the target file. If not found, blocks with exit 2
11
+ # and a descriptive error message.
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Edit"
15
+
16
+ set -euo pipefail
17
+ INPUT=$(cat)
18
+
19
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
20
+ OLD_STRING=$(echo "$INPUT" | jq -r '.tool_input.old_string // empty' 2>/dev/null)
21
+
22
+ # Skip if no file or old_string
23
+ [ -z "$FILE" ] && exit 0
24
+ [ -z "$OLD_STRING" ] && exit 0
25
+
26
+ # Skip if file doesn't exist (Edit tool will handle that error)
27
+ [ ! -f "$FILE" ] && exit 0
28
+
29
+ # Check if old_string exists in the file
30
+ if ! grep -qF "$OLD_STRING" "$FILE" 2>/dev/null; then
31
+ echo "BLOCKED: old_string not found in $FILE." >&2
32
+ echo "The file may have been modified by a prior edit in this batch." >&2
33
+ echo "Re-read the file to get the current content before retrying." >&2
34
+ exit 2
35
+ fi
36
+
37
+ exit 0
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # encoding-preserve-guard.sh — Warn when file encoding changes
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude's Write tool always outputs UTF-8. When editing files
7
+ # that use different encodings (UTF-8 BOM, Latin-1, Shift-JIS),
8
+ # the encoding silently changes, potentially corrupting content.
9
+ # Common in legacy codebases, .csv exports, Windows batch files.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Write|Edit"
13
+ # ================================================================
14
+
15
+ INPUT=$(cat)
16
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
17
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
18
+
19
+ [[ "$TOOL" != "Write" && "$TOOL" != "Edit" ]] && exit 0
20
+ [ -z "$FILE" ] || [ ! -f "$FILE" ] && exit 0
21
+
22
+ # Check for BOM (Byte Order Mark)
23
+ if head -c 3 "$FILE" 2>/dev/null | od -An -tx1 | grep -q "ef bb bf"; then
24
+ echo "WARNING: $FILE has UTF-8 BOM. Write tool may strip the BOM." >&2
25
+ fi
26
+
27
+ # Check for non-UTF-8 encoding using file command
28
+ ENCODING=$(file -bi "$FILE" 2>/dev/null | grep -oP 'charset=\K\S+')
29
+ if [ -n "$ENCODING" ] && [ "$ENCODING" != "utf-8" ] && [ "$ENCODING" != "us-ascii" ]; then
30
+ echo "WARNING: $FILE uses $ENCODING encoding. Write tool outputs UTF-8." >&2
31
+ echo " This may corrupt non-ASCII characters in the file." >&2
32
+ fi
33
+
34
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ # git-crypt-worktree-guard.sh — Block worktree creation in git-crypt repos
3
+ #
4
+ # Solves: When Claude creates a worktree in a git-crypt repo,
5
+ # the smudge filter fails because git-crypt hasn't been
6
+ # unlocked in the new worktree. This produces destructive
7
+ # commits that delete all encrypted files (#38538).
8
+ #
9
+ # How it works: Before git worktree add, checks if the repo
10
+ # uses git-crypt (.gitattributes contains filter=git-crypt).
11
+ # If yes, blocks the worktree creation with a warning.
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Bash"
15
+
16
+ set -euo pipefail
17
+ INPUT=$(cat)
18
+
19
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
20
+ [ -z "$COMMAND" ] && exit 0
21
+
22
+ # Only check git worktree add
23
+ if ! echo "$COMMAND" | grep -qE 'git\s+worktree\s+add'; then
24
+ exit 0
25
+ fi
26
+
27
+ # Check if repo uses git-crypt
28
+ if [ -f ".gitattributes" ] && grep -q "filter=git-crypt" .gitattributes 2>/dev/null; then
29
+ echo "BLOCKED: Cannot create worktree in a git-crypt repo." >&2
30
+ echo "git-crypt is not automatically unlocked in new worktrees." >&2
31
+ echo "This would produce destructive commits that delete all encrypted files." >&2
32
+ echo "Work in the main repo instead, or manually run 'git-crypt unlock' in the worktree first." >&2
33
+ exit 2
34
+ fi
35
+
36
+ exit 0
@@ -0,0 +1,99 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # git-operations-require-approval.sh — Block git write operations
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes ignores CLAUDE.md rules about git commit,
7
+ # push, and branch creation — performing these operations without
8
+ # user approval. This hook enforces the restriction at process level.
9
+ #
10
+ # Blocks:
11
+ # git commit, git push (including --force), git checkout -b,
12
+ # git switch -c, git branch <name>
13
+ #
14
+ # Does NOT block:
15
+ # git status, git log, git diff, git show, git branch (list),
16
+ # git fetch, git stash, git add
17
+ #
18
+ # Handles compound commands (&&, ;, ||) by checking each segment.
19
+ #
20
+ # See: https://github.com/anthropics/claude-code/issues/40695
21
+ #
22
+ # TRIGGER: PreToolUse MATCHER: "Bash"
23
+ # ================================================================
24
+
25
+ INPUT=$(cat)
26
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
27
+
28
+ [ -z "$COMMAND" ] && exit 0
29
+
30
+ # Skip if the command is inside echo/printf (not actual execution)
31
+ echo "$COMMAND" | grep -qE '^\s*(echo|printf)\s' && exit 0
32
+
33
+ # Check each segment of compound commands
34
+ check_segment() {
35
+ local seg="$1"
36
+ # Trim whitespace
37
+ seg=$(echo "$seg" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
38
+ [ -z "$seg" ] && return 0
39
+
40
+ # git commit
41
+ if echo "$seg" | grep -qE '\bgit\s+commit\b'; then
42
+ echo "BLOCKED: git commit requires explicit user approval." >&2
43
+ echo "Command: $seg" >&2
44
+ echo "" >&2
45
+ echo "See: https://github.com/anthropics/claude-code/issues/40695" >&2
46
+ return 1
47
+ fi
48
+
49
+ # git push (including force variants)
50
+ if echo "$seg" | grep -qE '\bgit\s+push\b'; then
51
+ echo "BLOCKED: git push requires explicit user approval." >&2
52
+ echo "Command: $seg" >&2
53
+ echo "" >&2
54
+ echo "See: https://github.com/anthropics/claude-code/issues/40695" >&2
55
+ return 1
56
+ fi
57
+
58
+ # git checkout -b (branch creation)
59
+ if echo "$seg" | grep -qE '\bgit\s+checkout\s+(-b|--branch)\b'; then
60
+ echo "BLOCKED: git branch creation requires explicit user approval." >&2
61
+ echo "Command: $seg" >&2
62
+ return 1
63
+ fi
64
+
65
+ # git switch -c / --create (branch creation)
66
+ if echo "$seg" | grep -qE '\bgit\s+switch\s+(-c|--create)\b'; then
67
+ echo "BLOCKED: git branch creation requires explicit user approval." >&2
68
+ echo "Command: $seg" >&2
69
+ return 1
70
+ fi
71
+
72
+ # git branch <name> (creation, not listing)
73
+ # git branch without flags or with only -a/-r/-l/--list is listing
74
+ if echo "$seg" | grep -qE '\bgit\s+branch\s'; then
75
+ # Allow listing flags
76
+ if echo "$seg" | grep -qE '\bgit\s+branch\s+(-[arl]|--list|--merged|--no-merged|--contains|-v|--verbose|-d|--delete|-D)\b'; then
77
+ return 0
78
+ fi
79
+ # If it has a name argument after "git branch", it's creation
80
+ local args
81
+ args=$(echo "$seg" | sed 's/.*\bgit\s\+branch\s\+//')
82
+ if [ -n "$args" ] && ! echo "$args" | grep -qE '^\s*$'; then
83
+ echo "BLOCKED: git branch creation requires explicit user approval." >&2
84
+ echo "Command: $seg" >&2
85
+ return 1
86
+ fi
87
+ fi
88
+
89
+ return 0
90
+ }
91
+
92
+ # Split on && ; || and check each part
93
+ while IFS= read -r segment; do
94
+ if ! check_segment "$segment"; then
95
+ exit 2
96
+ fi
97
+ done < <(echo "$COMMAND" | sed 's/&&/\n/g; s/;/\n/g; s/||/\n/g')
98
+
99
+ exit 0
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # line-ending-guard.sh — Warn on CRLF/LF mismatch
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code outputs LF line endings. On Windows/WSL, files may
7
+ # use CRLF. Editing a CRLF file with LF content creates mixed
8
+ # line endings, causing test failures, script errors, and git
9
+ # noise. Especially common in .bat, .cmd, .ps1 files.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Write|Edit"
13
+ # ================================================================
14
+
15
+ INPUT=$(cat)
16
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
17
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
18
+
19
+ [[ "$TOOL" != "Write" && "$TOOL" != "Edit" ]] && exit 0
20
+ [ -z "$FILE" ] || [ ! -f "$FILE" ] && exit 0
21
+
22
+ # Check if existing file uses CRLF
23
+ if head -c 1000 "$FILE" 2>/dev/null | od -c | grep -q '\\r\\n'; then
24
+ echo "WARNING: $FILE uses CRLF line endings. Claude outputs LF." >&2
25
+ echo " This may create mixed line endings. Consider:" >&2
26
+ echo " - Setting .gitattributes: *.bat text eol=crlf" >&2
27
+ echo " - Running: unix2dos $FILE (after edit)" >&2
28
+ fi
29
+
30
+ exit 0
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+ # permission-pattern-auto-allow.sh — Auto-allow commands matching user-defined patterns
3
+ #
4
+ # Solves: Claude repeatedly asks for permission to run commands
5
+ # even after "Always Allow" — because the settings use
6
+ # exact argument matching, not pattern matching (#819).
7
+ #
8
+ # How it works: Maintains a list of regex patterns in an env var
9
+ # or config file. If the Bash command matches any pattern,
10
+ # returns allow decision. Bypasses the broken exact-match
11
+ # permission system entirely.
12
+ #
13
+ # Config: Set ALLOWED_PATTERNS env var or create ~/.claude/allowed-patterns.txt
14
+ # Example patterns (one per line):
15
+ # ^npm (test|run|install|ci)
16
+ # ^git (status|log|diff|add|commit|push|pull|fetch|branch|checkout)
17
+ # ^(ls|cat|pwd|echo|head|tail|wc|grep|find|which|env)
18
+ # ^python[23]?\s
19
+ # ^cargo (build|test|run|check)
20
+ #
21
+ # TRIGGER: PreToolUse
22
+ # MATCHER: "Bash"
23
+
24
+ set -euo pipefail
25
+ INPUT=$(cat)
26
+
27
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
28
+ [ -z "$COMMAND" ] && exit 0
29
+
30
+ # Load patterns from file or env
31
+ PATTERN_FILE="${HOME}/.claude/allowed-patterns.txt"
32
+ if [ -f "$PATTERN_FILE" ]; then
33
+ while IFS= read -r pattern || [ -n "$pattern" ]; do
34
+ # Skip empty lines and comments
35
+ [[ -z "$pattern" || "$pattern" == \#* ]] && continue
36
+ if echo "$COMMAND" | grep -qE "$pattern" 2>/dev/null; then
37
+ exit 0
38
+ fi
39
+ done < "$PATTERN_FILE"
40
+ elif [ -n "${ALLOWED_PATTERNS:-}" ]; then
41
+ # Fallback: pipe-separated patterns in env var
42
+ echo "$ALLOWED_PATTERNS" | tr '|' '\n' | while IFS= read -r pattern; do
43
+ [ -z "$pattern" ] && continue
44
+ if echo "$COMMAND" | grep -qE "$pattern" 2>/dev/null; then
45
+ exit 0
46
+ fi
47
+ done
48
+ fi
49
+
50
+ exit 0
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # read-audit-log.sh — Log all file read operations for forensics
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Creates a searchable audit trail of every file Claude reads.
7
+ # Useful for:
8
+ # - Post-incident forensics ("what did Claude access?")
9
+ # - Detecting prompt injection (tracking reads of untrusted files)
10
+ # - Understanding context consumption (which files cost tokens)
11
+ #
12
+ # TRIGGER: PostToolUse
13
+ # MATCHER: "Read"
14
+ #
15
+ # OUTPUT: ~/.claude/read-audit.jsonl (append-only)
16
+ # ================================================================
17
+
18
+ INPUT=$(cat)
19
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
20
+ [ "$TOOL" != "Read" ] && exit 0
21
+
22
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
23
+ [ -z "$FILE" ] && exit 0
24
+
25
+ AUDIT_FILE="${CC_READ_AUDIT:-$HOME/.claude/read-audit.jsonl}"
26
+ mkdir -p "$(dirname "$AUDIT_FILE")"
27
+
28
+ # Get file metadata
29
+ SIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE" 2>/dev/null || echo 0)
30
+ LINES=$(wc -l < "$FILE" 2>/dev/null || echo 0)
31
+
32
+ echo "{\"time\":\"$(date -Iseconds)\",\"file\":\"$FILE\",\"size\":$SIZE,\"lines\":$LINES,\"cwd\":\"$(pwd)\"}" >> "$AUDIT_FILE"
33
+
34
+ exit 0
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # session-duration-guard.sh — Warn on long-running sessions
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Model quality degrades in very long sessions due to context
7
+ # accumulation, compaction artifacts, and attention dilution.
8
+ # This hook warns at configurable thresholds and suggests
9
+ # saving state + starting fresh.
10
+ #
11
+ # Based on 700+ hours of autonomous operation experience.
12
+ #
13
+ # TRIGGER: PostToolUse
14
+ # MATCHER: "" (all tools)
15
+ #
16
+ # CONFIG:
17
+ # CC_SESSION_WARN_HOURS=2 (warn after 2 hours, default)
18
+ # CC_SESSION_CRITICAL_HOURS=4 (critical after 4 hours, default)
19
+ # ================================================================
20
+
21
+ MARKER="/tmp/cc-session-start-$$"
22
+ WARN_HOURS="${CC_SESSION_WARN_HOURS:-2}"
23
+ CRITICAL_HOURS="${CC_SESSION_CRITICAL_HOURS:-4}"
24
+
25
+ # Create marker on first run
26
+ if [ ! -f "$MARKER" ]; then
27
+ date +%s > "$MARKER"
28
+ exit 0
29
+ fi
30
+
31
+ # Check every 50 tool calls (not every call)
32
+ COUNTER="/tmp/cc-duration-counter-$$"
33
+ COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0)
34
+ COUNT=$((COUNT + 1))
35
+ echo "$COUNT" > "$COUNTER"
36
+ [ $((COUNT % 50)) -ne 0 ] && exit 0
37
+
38
+ START=$(cat "$MARKER" 2>/dev/null || echo 0)
39
+ NOW=$(date +%s)
40
+ ELAPSED=$(( (NOW - START) / 3600 ))
41
+ ELAPSED_MIN=$(( (NOW - START) / 60 ))
42
+
43
+ if [ "$ELAPSED" -ge "$CRITICAL_HOURS" ]; then
44
+ echo "⚠ CRITICAL: Session running for ${ELAPSED_MIN} minutes (${ELAPSED}+ hours)." >&2
45
+ echo " Model quality typically degrades after ${CRITICAL_HOURS} hours." >&2
46
+ echo " Save your state and start a new session: /compact then resume later." >&2
47
+ elif [ "$ELAPSED" -ge "$WARN_HOURS" ]; then
48
+ echo "NOTE: Session running for ${ELAPSED_MIN} minutes. Consider saving state." >&2
49
+ fi
50
+
51
+ exit 0