cc-safe-setup 1.8.1 → 1.8.3

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
@@ -138,11 +138,15 @@ Need custom hooks beyond the 8 built-in ones? See [`examples/`](examples/) for r
138
138
  - **auto-approve-build.sh** — Auto-approve npm/yarn/cargo/go/python build, test, and lint commands
139
139
  - **auto-approve-docker.sh** — Auto-approve docker build, compose, ps, logs, and other safe commands
140
140
  - **block-database-wipe.sh** — Block destructive database commands: Laravel `migrate:fresh`, Django `flush`, Rails `db:drop`, raw `DROP DATABASE` ([#37405](https://github.com/anthropics/claude-code/issues/37405) [#37439](https://github.com/anthropics/claude-code/issues/37439))
141
+ - **auto-approve-python.sh** — Auto-approve pytest, mypy, ruff, black, isort, flake8, pylint commands
142
+ - **auto-snapshot.sh** — Auto-save file snapshots before edits for rollback protection ([#37386](https://github.com/anthropics/claude-code/issues/37386) [#37457](https://github.com/anthropics/claude-code/issues/37457))
143
+ - **allowlist.sh** — Block everything not explicitly approved — inverse permission model ([#37471](https://github.com/anthropics/claude-code/issues/37471))
144
+ - **protect-dotfiles.sh** — Block modifications to `~/.bashrc`, `~/.aws/`, `~/.ssh/` and chezmoi without diff ([#37478](https://github.com/anthropics/claude-code/issues/37478))
141
145
 
142
146
  ## Learn More
143
147
 
144
148
  - [Official Hooks Reference](https://code.claude.com/docs/en/hooks) — Claude Code hooks documentation
145
- - [Hooks Cookbook](https://github.com/yurukusa/claude-code-hooks/blob/main/COOKBOOK.md) — 12 ready-to-use recipes from real GitHub Issues
149
+ - [Hooks Cookbook](https://github.com/yurukusa/claude-code-hooks/blob/main/COOKBOOK.md) — 13 ready-to-use recipes from real GitHub Issues
146
150
  - [Japanese guide (Qiita)](https://qiita.com/yurukusa/items/a9714b33f5d974e8f1e8) — この記事の日本語解説
147
151
  - [The incident that inspired this tool](https://github.com/anthropics/claude-code/issues/36339) — NTFS junction rm -rf
148
152
 
@@ -0,0 +1,37 @@
1
+ # Example Hooks
2
+
3
+ Custom hooks beyond the 8 built-in ones. Copy any file to `~/.claude/hooks/` and add to `settings.json`.
4
+
5
+ | Hook | Purpose | Related Issue |
6
+ |------|---------|---------------|
7
+ | **allowlist.sh** | Block everything not explicitly approved (inverse model) | [#37471](https://github.com/anthropics/claude-code/issues/37471) |
8
+ | **auto-approve-build.sh** | Auto-approve npm/yarn/cargo/go build, test, lint | |
9
+ | **auto-approve-docker.sh** | Auto-approve docker build, compose, ps, logs | |
10
+ | **auto-approve-git-read.sh** | Auto-approve `git status/log/diff` even with `-C` flags | [#36900](https://github.com/anthropics/claude-code/issues/36900) |
11
+ | **auto-approve-python.sh** | Auto-approve pytest, mypy, ruff, black, isort | |
12
+ | **auto-approve-ssh.sh** | Auto-approve safe SSH commands (uptime, whoami) | |
13
+ | **auto-snapshot.sh** | Save file snapshots before edits (rollback protection) | [#37386](https://github.com/anthropics/claude-code/issues/37386) |
14
+ | **block-database-wipe.sh** | Block destructive DB commands (Laravel, Django, Rails) | [#37405](https://github.com/anthropics/claude-code/issues/37405) |
15
+ | **edit-guard.sh** | Block Edit/Write to protected files | [#37210](https://github.com/anthropics/claude-code/issues/37210) |
16
+ | **enforce-tests.sh** | Warn when source changes without test changes | |
17
+ | **notify-waiting.sh** | Desktop notification when Claude waits for input | |
18
+ | **protect-dotfiles.sh** | Block modifications to ~/.bashrc, ~/.aws/, ~/.ssh/ | [#37478](https://github.com/anthropics/claude-code/issues/37478) |
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ # 1. Copy example to hooks directory
24
+ cp examples/block-database-wipe.sh ~/.claude/hooks/
25
+
26
+ # 2. Make executable
27
+ chmod +x ~/.claude/hooks/block-database-wipe.sh
28
+
29
+ # 3. Add to settings.json
30
+ # See each file's header comment for the JSON configuration
31
+ ```
32
+
33
+ ## List from CLI
34
+
35
+ ```bash
36
+ npx cc-safe-setup --examples
37
+ ```
@@ -0,0 +1,64 @@
1
+ #!/bin/bash
2
+ # allowlist.sh — Only allow explicitly approved commands
3
+ #
4
+ # Inverts the default permission model: everything is blocked
5
+ # unless it matches an approved pattern. This is the opposite
6
+ # of cc-safe-setup's destructive-guard (which blocks specific
7
+ # dangerous commands).
8
+ #
9
+ # Use case: Highly sensitive environments where you want to
10
+ # enumerate exactly what Claude Code can do.
11
+ #
12
+ # Born from GitHub Issue #37471 (Immutable session manifest)
13
+ #
14
+ # Usage: Add to settings.json as a PreToolUse hook
15
+ #
16
+ # {
17
+ # "hooks": {
18
+ # "PreToolUse": [{
19
+ # "matcher": "Bash",
20
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/allowlist.sh" }]
21
+ # }]
22
+ # }
23
+ # }
24
+
25
+ INPUT=$(cat)
26
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
27
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
28
+
29
+ # Only gate Bash commands
30
+ [[ "$TOOL" != "Bash" ]] && exit 0
31
+ [[ -z "$COMMAND" ]] && exit 0
32
+
33
+ # ========================================
34
+ # ALLOWLIST — add your approved patterns
35
+ # ========================================
36
+ ALLOWED=(
37
+ # Git (read-only + commit, but not push/reset/clean)
38
+ "^\s*git (add|commit|diff|log|status|branch|show|stash|rev-parse|tag)"
39
+ # Package managers (install + read-only)
40
+ "^\s*npm (test|run|install|ci|ls|outdated)"
41
+ "^\s*pip (install|list|show|freeze)"
42
+ # Build/test/lint
43
+ "^\s*pytest"
44
+ "^\s*python3? -m (pytest|py_compile|unittest)"
45
+ "^\s*node --check"
46
+ "^\s*(ruff|black|isort|flake8|pylint|mypy|eslint|prettier)"
47
+ # Safe read-only commands
48
+ "^\s*(cat|head|tail|wc|sort|grep|find|ls|pwd|echo|date|which|whoami)"
49
+ "^\s*(curl -s|wget -q)"
50
+ # Directory navigation
51
+ "^\s*(cd|mkdir|touch)"
52
+ )
53
+
54
+ for pattern in "${ALLOWED[@]}"; do
55
+ if echo "$COMMAND" | grep -qE "$pattern"; then
56
+ exit 0 # Approved
57
+ fi
58
+ done
59
+
60
+ # Not in allowlist — block
61
+ echo "BLOCKED: Command not in allowlist" >&2
62
+ echo "Command: $COMMAND" >&2
63
+ echo "To approve, add a pattern to ~/.claude/hooks/allowlist.sh" >&2
64
+ exit 2
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # auto-snapshot.sh — Automatic file snapshots before every edit
3
+ #
4
+ # Solves: Claude reverting verified changes after encountering
5
+ # contradictory information (sycophantic capitulation)
6
+ # (#37386, #37457)
7
+ #
8
+ # How it works:
9
+ # - Runs as a PreToolUse hook on Edit/Write
10
+ # - Copies the file to ~/.claude/snapshots/ before modification
11
+ # - When Claude walks back correct work, diff the snapshot
12
+ # against the current file and restore
13
+ #
14
+ # Usage: Add to settings.json as a PreToolUse hook
15
+ #
16
+ # {
17
+ # "hooks": {
18
+ # "PreToolUse": [{
19
+ # "matcher": "",
20
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/auto-snapshot.sh" }]
21
+ # }]
22
+ # }
23
+ # }
24
+
25
+ INPUT=$(cat)
26
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
27
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
28
+
29
+ # Only snapshot Edit and Write operations
30
+ if [[ "$TOOL" != "Edit" && "$TOOL" != "Write" ]]; then
31
+ exit 0
32
+ fi
33
+
34
+ # Only snapshot existing files (Write to new files has nothing to save)
35
+ if [[ -z "$FILE" || ! -f "$FILE" ]]; then
36
+ exit 0
37
+ fi
38
+
39
+ SNAP_DIR="$HOME/.claude/snapshots/$(date +%Y%m%d)"
40
+ mkdir -p "$SNAP_DIR" 2>/dev/null
41
+
42
+ # Use filename + timestamp to avoid collisions
43
+ BASENAME=$(basename "$FILE")
44
+ TIMESTAMP=$(date +%H%M%S)
45
+ cp "$FILE" "$SNAP_DIR/${BASENAME}.${TIMESTAMP}" 2>/dev/null
46
+
47
+ # Keep snapshots manageable — delete files older than 7 days
48
+ find "$HOME/.claude/snapshots" -type f -mtime +7 -delete 2>/dev/null
49
+ find "$HOME/.claude/snapshots" -type d -empty -delete 2>/dev/null
50
+
51
+ exit 0
@@ -0,0 +1,81 @@
1
+ #!/bin/bash
2
+ # protect-dotfiles.sh — Block destructive operations on home directory config files
3
+ #
4
+ # Solves: Claude Code overwriting .bashrc, deleting .aws/, running
5
+ # chezmoi/stow without diffing first (#37478, #33391)
6
+ #
7
+ # Covers: Edit/Write to dotfiles, Bash commands that modify them
8
+ #
9
+ # Usage: Add to settings.json as a PreToolUse hook
10
+ #
11
+ # {
12
+ # "hooks": {
13
+ # "PreToolUse": [{
14
+ # "matcher": "",
15
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/protect-dotfiles.sh" }]
16
+ # }]
17
+ # }
18
+ # }
19
+
20
+ INPUT=$(cat)
21
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
22
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
23
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
24
+
25
+ # === Check 1: Edit/Write to dotfiles ===
26
+ if [[ "$TOOL" == "Edit" || "$TOOL" == "Write" ]]; then
27
+ HOME_DIR=$(eval echo "~")
28
+ PROTECTED_DOTFILES=(
29
+ ".bashrc" ".bash_profile" ".bash_logout"
30
+ ".zshrc" ".zprofile" ".zshenv"
31
+ ".profile" ".login"
32
+ ".gitconfig" ".gitignore_global"
33
+ ".ssh/config" ".ssh/authorized_keys"
34
+ ".aws/config" ".aws/credentials"
35
+ ".npmrc" ".yarnrc"
36
+ ".env" ".env.local" ".env.production"
37
+ )
38
+ for dotfile in "${PROTECTED_DOTFILES[@]}"; do
39
+ if [[ "$FILE" == "${HOME_DIR}/${dotfile}" ]]; then
40
+ echo "BLOCKED: Cannot modify ~/${dotfile}" >&2
41
+ echo "This is a critical config file. Edit it manually." >&2
42
+ exit 2
43
+ fi
44
+ done
45
+ # Block writing to any ~/.ssh/ or ~/.aws/ files
46
+ if [[ "$FILE" == "${HOME_DIR}/.ssh/"* || "$FILE" == "${HOME_DIR}/.aws/"* ]]; then
47
+ echo "BLOCKED: Cannot modify files in ${FILE%/*}/" >&2
48
+ exit 2
49
+ fi
50
+ fi
51
+
52
+ # === Check 2: Bash commands that modify dotfiles ===
53
+ if [[ "$TOOL" == "Bash" && -n "$CMD" ]]; then
54
+ # Skip echo/printf (string output, not actual modification)
55
+ if echo "$CMD" | grep -qE '^\s*(echo|printf|cat\s*<<)'; then
56
+ exit 0
57
+ fi
58
+
59
+ # Block chezmoi/stow apply without --dry-run
60
+ if echo "$CMD" | grep -qE '(chezmoi\s+(init|apply|update)|stow\s)' && \
61
+ ! echo "$CMD" | grep -qE '(--dry-run|--diff|-n\b|diff)'; then
62
+ echo "BLOCKED: Run 'chezmoi diff' or '--dry-run' first" >&2
63
+ echo "Command: $CMD" >&2
64
+ exit 2
65
+ fi
66
+
67
+ # Block rm on dotfile directories
68
+ if echo "$CMD" | grep -qE 'rm\s.*\.(ssh|aws|gnupg|config|local)'; then
69
+ echo "BLOCKED: Cannot delete dotfile directory" >&2
70
+ exit 2
71
+ fi
72
+
73
+ # Block cp/mv overwriting dotfiles without backup
74
+ if echo "$CMD" | grep -qE '(cp|mv)\s.*\.(bashrc|zshrc|profile|gitconfig)' && \
75
+ ! echo "$CMD" | grep -qE '(--backup|-b)'; then
76
+ echo "BLOCKED: Use --backup flag when overwriting dotfiles" >&2
77
+ exit 2
78
+ fi
79
+ fi
80
+
81
+ exit 0
package/index.mjs CHANGED
@@ -262,6 +262,9 @@ function examples() {
262
262
  'enforce-tests.sh': 'Warn when source files change without test files',
263
263
  'notify-waiting.sh': 'Desktop notification when Claude waits for input',
264
264
  'auto-approve-python.sh': 'Auto-approve pytest, mypy, ruff, black, isort commands',
265
+ 'auto-snapshot.sh': 'Auto-save file snapshots before edits (rollback protection)',
266
+ 'allowlist.sh': 'Block everything not in allowlist (inverse permission model)',
267
+ 'protect-dotfiles.sh': 'Block modifications to ~/.bashrc, ~/.aws/, ~/.ssh/',
265
268
  };
266
269
 
267
270
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "One command to make Claude Code safe for autonomous operation. 8 hooks: destructive blocker, branch guard, force-push protection, secret leak prevention, syntax checks, and more.",
5
5
  "main": "index.mjs",
6
6
  "bin": {