cc-safe-setup 1.8.2 → 1.8.4
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 +4 -1
- package/examples/README.md +38 -0
- package/examples/allowlist.sh +64 -0
- package/examples/protect-dotfiles.sh +81 -0
- package/examples/scope-guard.sh +56 -0
- package/index.mjs +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,11 +140,14 @@ Need custom hooks beyond the 8 built-in ones? See [`examples/`](examples/) for r
|
|
|
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
141
|
- **auto-approve-python.sh** — Auto-approve pytest, mypy, ruff, black, isort, flake8, pylint commands
|
|
142
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))
|
|
145
|
+
- **scope-guard.sh** — Block file operations outside project directory — absolute paths, home, parent escapes ([#36233](https://github.com/anthropics/claude-code/issues/36233))
|
|
143
146
|
|
|
144
147
|
## Learn More
|
|
145
148
|
|
|
146
149
|
- [Official Hooks Reference](https://code.claude.com/docs/en/hooks) — Claude Code hooks documentation
|
|
147
|
-
- [Hooks Cookbook](https://github.com/yurukusa/claude-code-hooks/blob/main/COOKBOOK.md) —
|
|
150
|
+
- [Hooks Cookbook](https://github.com/yurukusa/claude-code-hooks/blob/main/COOKBOOK.md) — 13 ready-to-use recipes from real GitHub Issues
|
|
148
151
|
- [Japanese guide (Qiita)](https://qiita.com/yurukusa/items/a9714b33f5d974e8f1e8) — この記事の日本語解説
|
|
149
152
|
- [The incident that inspired this tool](https://github.com/anthropics/claude-code/issues/36339) — NTFS junction rm -rf
|
|
150
153
|
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
| **scope-guard.sh** | Block file operations outside project directory | [#36233](https://github.com/anthropics/claude-code/issues/36233) |
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 1. Copy example to hooks directory
|
|
25
|
+
cp examples/block-database-wipe.sh ~/.claude/hooks/
|
|
26
|
+
|
|
27
|
+
# 2. Make executable
|
|
28
|
+
chmod +x ~/.claude/hooks/block-database-wipe.sh
|
|
29
|
+
|
|
30
|
+
# 3. Add to settings.json
|
|
31
|
+
# See each file's header comment for the JSON configuration
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## List from CLI
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx cc-safe-setup --examples
|
|
38
|
+
```
|
|
@@ -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,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
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scope-guard.sh — Block file operations outside the project directory
|
|
3
|
+
#
|
|
4
|
+
# Solves: Claude Code deleting files on Desktop, in ~/Applications,
|
|
5
|
+
# or anywhere outside the working directory (#36233, #36339)
|
|
6
|
+
#
|
|
7
|
+
# Usage: Add to settings.json as a PreToolUse hook
|
|
8
|
+
#
|
|
9
|
+
# {
|
|
10
|
+
# "hooks": {
|
|
11
|
+
# "PreToolUse": [{
|
|
12
|
+
# "matcher": "Bash",
|
|
13
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/scope-guard.sh" }]
|
|
14
|
+
# }]
|
|
15
|
+
# }
|
|
16
|
+
# }
|
|
17
|
+
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
20
|
+
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
21
|
+
|
|
22
|
+
[[ "$TOOL" != "Bash" ]] && exit 0
|
|
23
|
+
[[ -z "$CMD" ]] && exit 0
|
|
24
|
+
|
|
25
|
+
# Skip string output commands
|
|
26
|
+
if echo "$CMD" | grep -qE '^\s*(echo|printf|cat\s*<<)'; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Check for destructive commands with paths outside project
|
|
31
|
+
if echo "$CMD" | grep -qE '\brm\b.*(-[a-zA-Z]*[rf]|--(recursive|force))'; then
|
|
32
|
+
# Block absolute paths
|
|
33
|
+
if echo "$CMD" | grep -qE '\brm\b[^|;]*\s+/[a-zA-Z]'; then
|
|
34
|
+
echo "BLOCKED: rm with absolute path" >&2
|
|
35
|
+
echo "Command: $CMD" >&2
|
|
36
|
+
exit 2
|
|
37
|
+
fi
|
|
38
|
+
# Block home directory paths
|
|
39
|
+
if echo "$CMD" | grep -qE '\brm\b[^|;]*\s+~/'; then
|
|
40
|
+
echo "BLOCKED: rm targeting home directory" >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
# Block parent directory escapes
|
|
44
|
+
if echo "$CMD" | grep -qE '\brm\b[^|;]*\s+\.\./'; then
|
|
45
|
+
echo "BLOCKED: rm escaping project directory" >&2
|
|
46
|
+
exit 2
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Block targeting well-known user/system directories
|
|
51
|
+
if echo "$CMD" | grep -qiE '\b(rm|del|Remove-Item)\b.*(Desktop|Applications|Documents|Downloads|Library|Keychain|\.aws|\.ssh)'; then
|
|
52
|
+
echo "BLOCKED: targeting system/user directory" >&2
|
|
53
|
+
exit 2
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -263,6 +263,9 @@ function examples() {
|
|
|
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
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/',
|
|
268
|
+
'scope-guard.sh': 'Block file operations outside project directory',
|
|
266
269
|
};
|
|
267
270
|
|
|
268
271
|
console.log();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
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": {
|