opsentry 1.8.0__py3-none-any.whl

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 (61) hide show
  1. opsentry/VERSION +1 -0
  2. opsentry/__init__.py +5 -0
  3. opsentry/cli.py +295 -0
  4. opsentry/config/__init__.py +0 -0
  5. opsentry/config/generate.py +414 -0
  6. opsentry/config/hook_templates/block-dangerous-commands.sh.j2 +102 -0
  7. opsentry/config/hook_templates/block-data-exfiltration.sh.j2 +94 -0
  8. opsentry/config/hook_templates/block-environment-escape.sh.j2 +99 -0
  9. opsentry/config/hook_templates/block-git-commands.sh.j2 +46 -0
  10. opsentry/config/hook_templates/block-package-install.sh.j2 +95 -0
  11. opsentry/config/hook_templates/block-pii-leakage.sh.j2 +100 -0
  12. opsentry/config/hook_templates/block-scope-escape.sh.j2 +115 -0
  13. opsentry/config/hook_templates/block-sensitive-files.sh.j2 +97 -0
  14. opsentry/config/init_wizard.py +201 -0
  15. opsentry/config/rule_texts/01_sensitive_files.md +15 -0
  16. opsentry/config/rule_texts/02_client_data.md +10 -0
  17. opsentry/config/rule_texts/03_credentials.md +17 -0
  18. opsentry/config/rule_texts/04_sql_safety.md +33 -0
  19. opsentry/config/rule_texts/05_dangerous_code.md +29 -0
  20. opsentry/config/rule_texts/06_frontend_xss.md +9 -0
  21. opsentry/config/rule_texts/07_git_operations_block_all.md +13 -0
  22. opsentry/config/rule_texts/07_git_operations_read_only.md +18 -0
  23. opsentry/config/rule_texts/08_destructive_commands.md +12 -0
  24. opsentry/config/rule_texts/09_database_safety.md +17 -0
  25. opsentry/config/rule_texts/10_network_requests.md +10 -0
  26. opsentry/config/rule_texts/11_code_quality.md +13 -0
  27. opsentry/config/rule_texts/12_data_exfiltration.md +10 -0
  28. opsentry/config/rule_texts/13_package_install.md +9 -0
  29. opsentry/config/rule_texts/14_scope_boundaries.md +8 -0
  30. opsentry/config/rule_texts/15_env_isolation.md +9 -0
  31. opsentry/config/rule_texts/16_resource_limits.md +8 -0
  32. opsentry/config/rule_texts/17_compliance_privacy.md +9 -0
  33. opsentry/config/rule_texts/18_when_in_doubt.md +10 -0
  34. opsentry/config/schema.py +142 -0
  35. opsentry/config/templates/generic.yaml +84 -0
  36. opsentry/deployment/.claude/commands/code-health.md +42 -0
  37. opsentry/deployment/.claude/commands/governance-check.md +29 -0
  38. opsentry/deployment/.claude/commands/security-audit.md +41 -0
  39. opsentry/deployment/baseline.py +440 -0
  40. opsentry/deployment/blocklog_audit.py +497 -0
  41. opsentry/deployment/claude/CLAUDE.md +300 -0
  42. opsentry/deployment/claude/hooks/block-dangerous-commands.sh +106 -0
  43. opsentry/deployment/claude/hooks/block-data-exfiltration.sh +99 -0
  44. opsentry/deployment/claude/hooks/block-environment-escape.sh +96 -0
  45. opsentry/deployment/claude/hooks/block-git-commands.sh +37 -0
  46. opsentry/deployment/claude/hooks/block-package-install.sh +103 -0
  47. opsentry/deployment/claude/hooks/block-pii-leakage.sh +86 -0
  48. opsentry/deployment/claude/hooks/block-scope-escape.sh +210 -0
  49. opsentry/deployment/claude/hooks/block-sensitive-files.sh +97 -0
  50. opsentry/deployment/claude/settings.json +72 -0
  51. opsentry/deployment/install.sh +343 -0
  52. opsentry/deployment/patrol.sh +266 -0
  53. opsentry/deployment/test.sh +728 -0
  54. opsentry/deployment/update.sh +19 -0
  55. opsentry/deployment/verify.sh +201 -0
  56. opsentry-1.8.0.dist-info/METADATA +158 -0
  57. opsentry-1.8.0.dist-info/RECORD +61 -0
  58. opsentry-1.8.0.dist-info/WHEEL +5 -0
  59. opsentry-1.8.0.dist-info/entry_points.txt +2 -0
  60. opsentry-1.8.0.dist-info/licenses/LICENSE +190 -0
  61. opsentry-1.8.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,99 @@
1
+ #!/bin/bash
2
+ # block-environment-escape.sh
3
+ # PreToolUse hook — blocks Claude Code from escaping the local dev environment.
4
+ # Blocks SSH/SCP, Docker run/exec/build, production env vars, destructive Terraform/kubectl.
5
+ # Exit code 2 = block the action, stderr message goes to Claude as feedback.
6
+
7
+ set -euo pipefail
8
+
9
+ LOG_FILE="$HOME/.claude/guardrail-blocks.log"
10
+ HOOK_NAME="block-environment-escape"
11
+
12
+ log_block() {
13
+ local reason="$1"
14
+ local detail="$2"
15
+ local timestamp
16
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
17
+ echo "{\"timestamp\":\"$timestamp\",\"hook\":\"$HOOK_NAME\",\"reason\":\"$reason\",\"detail\":\"$detail\",\"user\":\"$(whoami)\"}" >> "$LOG_FILE"
18
+ }
19
+
20
+ INPUT=$(cat)
21
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
22
+
23
+ # Only check Bash tool
24
+ if [ "$TOOL_NAME" != "Bash" ]; then
25
+ exit 0
26
+ fi
27
+
28
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
29
+
30
+ # --- SSH / SCP ---
31
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)(ssh|scp)\s'; then
32
+ log_block "SSH/SCP command detected" "$COMMAND"
33
+ echo "BLOCKED: SSH and SCP commands are not allowed. The AI agent must not connect to remote hosts. Write the command for the developer to review and run manually." >&2
34
+ exit 2
35
+ fi
36
+
37
+ # --- rsync to remote ---
38
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)rsync\s.*:'; then
39
+ log_block "rsync to remote detected" "$COMMAND"
40
+ echo "BLOCKED: rsync to remote hosts is not allowed. Local rsync operations are permitted." >&2
41
+ exit 2
42
+ fi
43
+
44
+ {% if infra_tools.get("docker", "read_only") == "block" %}
45
+ # --- Docker (all blocked) ---
46
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)docker\s'; then
47
+ log_block "Docker command detected" "$COMMAND"
48
+ echo "BLOCKED: All Docker commands are blocked by policy. Write the command for the developer to review and run manually." >&2
49
+ exit 2
50
+ fi
51
+ {% elif infra_tools.get("docker", "read_only") == "read_only" %}
52
+ # --- Docker (write commands blocked, read-only allowed) ---
53
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)docker\s+(run|exec|cp|build|push|pull|commit|tag|save|load|import|export)\s'; then
54
+ log_block "Docker write command detected" "$COMMAND"
55
+ echo "BLOCKED: Docker run/exec/cp/build commands are not allowed. Read-only commands (ps, logs, images, inspect) are permitted." >&2
56
+ exit 2
57
+ fi
58
+ {% endif %}
59
+
60
+ # --- Production environment variables ---
61
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)(export\s+|env\s+)?(PROD_|PRODUCTION_)'; then
62
+ log_block "Production env var detected" "$COMMAND"
63
+ echo "BLOCKED: Setting or reading production environment variables (PROD_*, PRODUCTION_*) is not allowed." >&2
64
+ exit 2
65
+ fi
66
+
67
+ {% if infra_tools.get("terraform", "read_only") == "block" %}
68
+ # --- Terraform (all blocked) ---
69
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)terraform\s'; then
70
+ log_block "Terraform command detected" "$COMMAND"
71
+ echo "BLOCKED: All Terraform commands are blocked by policy." >&2
72
+ exit 2
73
+ fi
74
+ {% elif infra_tools.get("terraform", "read_only") == "read_only" %}
75
+ # --- Terraform (destructive blocked, read-only allowed) ---
76
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)terraform\s+(apply|destroy|import)\s*'; then
77
+ log_block "Destructive Terraform command detected" "$COMMAND"
78
+ echo "BLOCKED: terraform apply/destroy/import are not allowed. Read-only commands (plan, validate, fmt, show, state list) are permitted." >&2
79
+ exit 2
80
+ fi
81
+ {% endif %}
82
+
83
+ {% if infra_tools.get("kubectl", "read_only") == "block" %}
84
+ # --- kubectl (all blocked) ---
85
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)kubectl\s'; then
86
+ log_block "kubectl command detected" "$COMMAND"
87
+ echo "BLOCKED: All kubectl commands are blocked by policy." >&2
88
+ exit 2
89
+ fi
90
+ {% elif infra_tools.get("kubectl", "read_only") == "read_only" %}
91
+ # --- kubectl (destructive blocked, read-only allowed) ---
92
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)kubectl\s+(delete|exec|apply|patch|replace|edit|scale|rollout)\s'; then
93
+ log_block "Destructive kubectl command detected" "$COMMAND"
94
+ echo "BLOCKED: kubectl delete/exec/apply are not allowed. Read-only commands (get, describe, logs, top) are permitted." >&2
95
+ exit 2
96
+ fi
97
+ {% endif %}
98
+
99
+ exit 0
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # block-git-commands.sh
3
+ # PreToolUse hook — blocks Claude Code from executing git commands
4
+ # Exit code 2 = block the action, stderr message goes to Claude as feedback
5
+
6
+ set -euo pipefail
7
+
8
+ LOG_FILE="$HOME/.claude/guardrail-blocks.log"
9
+ HOOK_NAME="block-git-commands"
10
+
11
+ log_block() {
12
+ local reason="$1"
13
+ local detail="$2"
14
+ local timestamp
15
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
16
+ echo "{\"timestamp\":\"$timestamp\",\"hook\":\"$HOOK_NAME\",\"reason\":\"$reason\",\"detail\":\"$detail\",\"user\":\"$(whoami)\"}" >> "$LOG_FILE"
17
+ }
18
+
19
+ INPUT=$(cat)
20
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
21
+
22
+ # Only check Bash tool
23
+ if [ "$TOOL_NAME" != "Bash" ]; then
24
+ exit 0
25
+ fi
26
+
27
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
28
+
29
+ {% if git_policy == "block_all" %}
30
+ # Block ALL git commands
31
+ if echo "$COMMAND" | grep -qE '(^|\s|;|&&|\|\|)git\s'; then
32
+ log_block "Git command detected" "$COMMAND"
33
+ echo "BLOCKED: Git commands are not allowed. The developer manages version control. Provide git commands as text for the developer to review and run manually." >&2
34
+ exit 2
35
+ fi
36
+ {% elif git_policy == "read_only" %}
37
+ # Block git write commands, allow read-only (status, log, diff, show, branch list, remote -v, describe)
38
+ GIT_WRITE_CMDS='(commit|push|pull|merge|reset|rebase|checkout|stash|cherry-pick|tag|branch\s+-[dD]|push\s+--force)'
39
+ if echo "$COMMAND" | grep -qE "(^|\s|;|&&|\|\|)git\s+$GIT_WRITE_CMDS"; then
40
+ log_block "Git write command detected" "$COMMAND"
41
+ echo "BLOCKED: Git write commands are not allowed. You may use read-only commands (status, log, diff, show, branch). Provide write commands as text for the developer to review." >&2
42
+ exit 2
43
+ fi
44
+ {% endif %}
45
+
46
+ exit 0
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+ # block-package-install.sh
3
+ # PreToolUse hook that blocks package installations from untrusted or arbitrary sources.
4
+ # It detects pip, npm, gem, and go installs from custom registries, git URLs, and direct URLs.
5
+ # Curl/wget piped to package managers is also blocked.
6
+
7
+ set -euo pipefail
8
+
9
+ LOG_FILE="$HOME/.claude/guardrail-blocks.log"
10
+ HOOK_NAME="block-package-install"
11
+
12
+ log_block() {
13
+ local reason="$1"
14
+ local detail="$2"
15
+ local timestamp
16
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
17
+ echo "{\"timestamp\":\"$timestamp\",\"hook\":\"$HOOK_NAME\",\"reason\":\"$reason\",\"detail\":\"$detail\",\"user\":\"$(whoami)\"}" >> "$LOG_FILE"
18
+ }
19
+
20
+ INPUT=$(cat)
21
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
22
+
23
+ # Only check Bash commands
24
+ if [ "$TOOL_NAME" != "Bash" ]; then
25
+ exit 0
26
+ fi
27
+
28
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
29
+
30
+ # === PIP INSTALL FROM ARBITRARY SOURCES ===
31
+ if echo "$COMMAND" | grep -qE 'pip[3]?\s+install\s.*(-i\s|--index-url\s)'; then
32
+ log_block "pip install with custom index URL" "$COMMAND"
33
+ echo "BLOCKED: pip install with --index-url or -i pointing to a custom registry is not allowed. Use the default PyPI registry." >&2
34
+ exit 2
35
+ fi
36
+
37
+ if echo "$COMMAND" | grep -qE 'pip[3]?\s+install\s.*git\+'; then
38
+ log_block "pip install from git URL" "$COMMAND"
39
+ echo "BLOCKED: pip install from a git URL is not allowed. Only install packages from PyPI." >&2
40
+ exit 2
41
+ fi
42
+
43
+ if echo "$COMMAND" | grep -qE 'pip[3]?\s+install\s.*https?://'; then
44
+ log_block "pip install from direct URL" "$COMMAND"
45
+ echo "BLOCKED: pip install from a direct URL is not allowed. Only install packages from PyPI." >&2
46
+ exit 2
47
+ fi
48
+
49
+ # === NPM INSTALL FROM ARBITRARY SOURCES ===
50
+ if echo "$COMMAND" | grep -qE 'npm\s+install\s.*--registry\s'; then
51
+ log_block "npm install with custom registry" "$COMMAND"
52
+ echo "BLOCKED: npm install with --registry pointing to a custom registry is not allowed. Use the default npm registry." >&2
53
+ exit 2
54
+ fi
55
+
56
+ if echo "$COMMAND" | grep -qE 'npm\s+install\s.*git(\+https?|\+ssh|://)'; then
57
+ log_block "npm install from git URL" "$COMMAND"
58
+ echo "BLOCKED: npm install from a git URL is not allowed. Only install packages from the npm registry." >&2
59
+ exit 2
60
+ fi
61
+
62
+ if echo "$COMMAND" | grep -qE 'npm\s+install\s.*https?://'; then
63
+ log_block "npm install from tarball URL" "$COMMAND"
64
+ echo "BLOCKED: npm install from a direct URL is not allowed. Only install packages from the npm registry." >&2
65
+ exit 2
66
+ fi
67
+
68
+ if echo "$COMMAND" | grep -qE 'npm\s+install\s+[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+'; then
69
+ log_block "npm install from GitHub shorthand" "$COMMAND"
70
+ echo "BLOCKED: npm install from GitHub shorthand (user/repo) is not allowed. Only install packages from the npm registry." >&2
71
+ exit 2
72
+ fi
73
+
74
+ # === GEM INSTALL FROM ARBITRARY SOURCES ===
75
+ if echo "$COMMAND" | grep -qE 'gem\s+install\s.*--source\s'; then
76
+ log_block "gem install with custom source" "$COMMAND"
77
+ echo "BLOCKED: gem install with --source pointing to a custom registry is not allowed. Use the default RubyGems registry." >&2
78
+ exit 2
79
+ fi
80
+
81
+ # === GO INSTALL FROM ARBITRARY SOURCES ===
82
+ if echo "$COMMAND" | grep -qE 'go\s+install\s+https?://'; then
83
+ log_block "go install from URL" "$COMMAND"
84
+ echo "BLOCKED: go install from a direct URL is not allowed. Use standard Go module paths." >&2
85
+ exit 2
86
+ fi
87
+
88
+ # === CURL/WGET PIPED TO PACKAGE MANAGERS ===
89
+ if echo "$COMMAND" | grep -qE '(curl|wget)\s.*\|\s*(pip[3]?|npm|gem|go)\s'; then
90
+ log_block "curl/wget piped to package manager" "$COMMAND"
91
+ echo "BLOCKED: Piping curl/wget output to a package manager is not allowed. Download files first, review them, then install." >&2
92
+ exit 2
93
+ fi
94
+
95
+ exit 0
@@ -0,0 +1,100 @@
1
+ #!/bin/bash
2
+ # block-pii-leakage.sh
3
+ # PreToolUse hook -- blocks Claude Code from writing PII patterns into source files.
4
+ # Checks Write and Edit tool inputs for SSNs, credit card numbers, and similar patterns.
5
+ # Exit code 2 = block the action, stderr message goes to Claude as feedback.
6
+
7
+ set -euo pipefail
8
+
9
+ LOG_FILE="$HOME/.claude/guardrail-blocks.log"
10
+ HOOK_NAME="block-pii-leakage"
11
+
12
+ log_block() {
13
+ local reason="$1"
14
+ local detail="$2"
15
+ local timestamp
16
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
17
+ echo "{\"timestamp\":\"$timestamp\",\"hook\":\"$HOOK_NAME\",\"reason\":\"$reason\",\"detail\":\"$detail\",\"user\":\"$(whoami)\"}" >> "$LOG_FILE"
18
+ }
19
+
20
+ INPUT=$(cat)
21
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
22
+
23
+ # Only check Write, Edit, and Bash tools
24
+ if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ] && [ "$TOOL_NAME" != "Bash" ]; then
25
+ exit 0
26
+ fi
27
+
28
+ # Extract the content to scan based on tool type
29
+ CONTENT=""
30
+ if [ "$TOOL_NAME" = "Write" ]; then
31
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
32
+ elif [ "$TOOL_NAME" = "Edit" ]; then
33
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
34
+ elif [ "$TOOL_NAME" = "Bash" ]; then
35
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
36
+ fi
37
+
38
+ if [ -z "$CONTENT" ]; then
39
+ exit 0
40
+ fi
41
+
42
+ {% if "US" in pii_locales %}
43
+ # ============================================================
44
+ # Default PII patterns (US locale)
45
+ # ============================================================
46
+
47
+ # --- US Social Security Number (XXX-XX-XXXX) ---
48
+ SSN_MATCH=$(echo "$CONTENT" | grep -oE '\b[0-9]{3}-[0-9]{2}-[0-9]{4}\b' | head -1 || true)
49
+ if [ -n "$SSN_MATCH" ]; then
50
+ # Allow well-known synthetic test SSNs
51
+ IS_SYNTHETIC=$(echo "$SSN_MATCH" | grep -cE '^({{ synthetic_ssns | join("|") }})$' || true)
52
+ if [ "$IS_SYNTHETIC" -eq 0 ]; then
53
+ log_block "US SSN pattern detected" "$SSN_MATCH"
54
+ echo "BLOCKED: Content contains a pattern matching a US Social Security Number ($SSN_MATCH). Use synthetic test data like {{ synthetic_ssns[0] }} or {{ synthetic_ssns[1] }} instead. Never include real PII in source code." >&2
55
+ exit 2
56
+ fi
57
+ fi
58
+
59
+ # --- Credit Card Numbers (Visa, Mastercard, Amex, Discover) ---
60
+ CC_MATCH=$(echo "$CONTENT" | grep -oE '\b(4[0-9]{3}|5[1-5][0-9]{2}|6(011|5[0-9]{2}))[- ]?[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{1,4}\b' | head -1 || true)
61
+ if [ -z "$CC_MATCH" ]; then
62
+ # Try Amex pattern separately: 3[47]XX-XXXXXX-XXXXX
63
+ CC_MATCH=$(echo "$CONTENT" | grep -oE '\b3[47][0-9]{2}[- ]?[0-9]{6}[- ]?[0-9]{5}\b' | head -1 || true)
64
+ fi
65
+ if [ -n "$CC_MATCH" ]; then
66
+ log_block "Credit card number pattern detected" "$CC_MATCH"
67
+ echo "BLOCKED: Content contains a pattern matching a credit card number. Use synthetic test data like 4111-1111-1111-1111 instead. Never include real PII in source code." >&2
68
+ exit 2
69
+ fi
70
+ {% endif %}
71
+
72
+ {% if "KR" in pii_locales %}
73
+ # ============================================================
74
+ # Optional locale: KR (Korean Resident Registration Number)
75
+ # ============================================================
76
+
77
+ # --- Korean Resident Registration Number (YYMMDD-NNNNNNN) ---
78
+ RRN_MATCH=$(echo "$CONTENT" | grep -oE '\b[0-9]{6}-[0-9]{7}\b' | head -1 || true)
79
+ if [ -n "$RRN_MATCH" ]; then
80
+ log_block "Korean RRN pattern detected" "$RRN_MATCH"
81
+ echo "BLOCKED: Content contains a pattern matching a Korean Resident Registration Number ($RRN_MATCH). Use synthetic test data instead. Never include real PII in source code." >&2
82
+ exit 2
83
+ fi
84
+ {% endif %}
85
+
86
+ {% if "EU" in pii_locales %}
87
+ # ============================================================
88
+ # Optional locale: EU (IBAN)
89
+ # ============================================================
90
+
91
+ # --- IBAN (International Bank Account Number) ---
92
+ IBAN_MATCH=$(echo "$CONTENT" | grep -oE '\b[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}\b' | head -1 || true)
93
+ if [ -n "$IBAN_MATCH" ]; then
94
+ log_block "IBAN pattern detected" "$IBAN_MATCH"
95
+ echo "BLOCKED: Content contains a pattern matching an IBAN ($IBAN_MATCH). Use synthetic test data instead. Never include real PII in source code." >&2
96
+ exit 2
97
+ fi
98
+ {% endif %}
99
+
100
+ exit 0
@@ -0,0 +1,115 @@
1
+ #!/bin/bash
2
+ # block-scope-escape.sh -- PreToolUse hook that prevents the agent from modifying
3
+ # its own guardrails in ~/.claude/ and from writing to dangerous system locations.
4
+ # Exit code 2 = block the action, stderr message goes to Claude as feedback.
5
+
6
+ set -euo pipefail
7
+
8
+ LOG_FILE="$HOME/.claude/guardrail-blocks.log"
9
+ HOOK_NAME="block-scope-escape"
10
+
11
+ log_block() {
12
+ local reason="$1"
13
+ local detail="$2"
14
+ local timestamp
15
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
16
+ echo "{\"timestamp\":\"$timestamp\",\"hook\":\"$HOOK_NAME\",\"reason\":\"$reason\",\"detail\":\"$detail\",\"user\":\"$(whoami)\"}" >> "$LOG_FILE"
17
+ }
18
+
19
+ INPUT=$(cat)
20
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # A. Self-modification prevention -- all tool types
24
+ # ---------------------------------------------------------------------------
25
+
26
+ # For file-based tools (Read, Write, Edit): check file_path
27
+ if [ "$TOOL_NAME" = "Read" ] || [ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ]; then
28
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
29
+
30
+ if [ -n "$FILE_PATH" ]; then
31
+ if echo "$FILE_PATH" | grep -qE '(/\.claude/|^~/.claude)'; then
32
+ log_block "Self-modification via $TOOL_NAME" "$FILE_PATH"
33
+ echo "BLOCKED: Access to '$FILE_PATH' is denied. Modifying files in ~/.claude/ is forbidden -- this directory contains security guardrails that must not be read or altered by the agent." >&2
34
+ exit 2
35
+ fi
36
+ fi
37
+
38
+ # File-based tools have no scope-boundary check; allow
39
+ exit 0
40
+ fi
41
+
42
+ # For Bash tool: check command text
43
+ if [ "$TOOL_NAME" = "Bash" ]; then
44
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
45
+
46
+ # --- A. Self-modification in Bash commands ---
47
+ CLAUDE_DIR_PATTERNS=(
48
+ '~/.claude'
49
+ '\$HOME/.claude'
50
+ '/\.claude/'
51
+ )
52
+ for pattern in "${CLAUDE_DIR_PATTERNS[@]}"; do
53
+ if echo "$COMMAND" | grep -qF "$pattern" 2>/dev/null || echo "$COMMAND" | grep -qE "$pattern" 2>/dev/null; then
54
+ log_block "Self-modification via Bash" "$COMMAND"
55
+ echo "BLOCKED: Command references ~/.claude/ directory. Modifying guardrail files is forbidden -- this directory contains security guardrails that must not be altered by the agent." >&2
56
+ exit 2
57
+ fi
58
+ done
59
+
60
+ # --- B. Scope boundary enforcement -- block writes to system locations ---
61
+ # Only trigger when the command contains a write indicator
62
+ WRITE_INDICATORS='(>|>>|(^|[[:space:]])cp[[:space:]]|(^|[[:space:]])mv[[:space:]]|(^|[[:space:]])tee[[:space:]]|(^|[[:space:]])install[[:space:]]|(^|[[:space:]])rsync[[:space:]])'
63
+
64
+ if echo "$COMMAND" | grep -qE "$WRITE_INDICATORS"; then
65
+ # System paths that must not be written to
66
+ BLOCKED_SYSTEM_PATHS=(
67
+ '/etc/'
68
+ '/usr/'
69
+ '/opt/'
70
+ '/System/'
71
+ '/Library/'
72
+ )
73
+ for sys_path in "${BLOCKED_SYSTEM_PATHS[@]}"; do
74
+ if echo "$COMMAND" | grep -qF "$sys_path"; then
75
+ # Allow /var/log for legitimate logging
76
+ if [ "$sys_path" = "/var/" ] && echo "$COMMAND" | grep -qF "/var/log"; then
77
+ continue
78
+ fi
79
+ log_block "Write to system path: $sys_path" "$COMMAND"
80
+ echo "BLOCKED: Command attempts to write to '$sys_path'. Writing to system directories is forbidden. Limit file operations to your project directory." >&2
81
+ exit 2
82
+ fi
83
+ done
84
+
85
+ # /var/ checked separately to allow /var/log
86
+ if echo "$COMMAND" | grep -qF "/var/"; then
87
+ if ! echo "$COMMAND" | grep -qE '/var/log(/|$|[[:space:]])'; then
88
+ log_block "Write to system path: /var/" "$COMMAND"
89
+ echo "BLOCKED: Command attempts to write to '/var/'. Writing to system directories is forbidden (except /var/log). Limit file operations to your project directory." >&2
90
+ exit 2
91
+ fi
92
+ fi
93
+
94
+ # Shell config files
95
+ SHELL_CONFIGS=(
96
+ '.bashrc'
97
+ '.zshrc'
98
+ '.bash_profile'
99
+ '.zprofile'
100
+ '.profile'
101
+ )
102
+ for config in "${SHELL_CONFIGS[@]}"; do
103
+ if echo "$COMMAND" | grep -qF "$config"; then
104
+ log_block "Write to shell config: $config" "$COMMAND"
105
+ echo "BLOCKED: Command attempts to modify shell config file '$config'. Modifying shell configuration is forbidden. Ask the developer to make shell changes manually." >&2
106
+ exit 2
107
+ fi
108
+ done
109
+ fi
110
+
111
+ exit 0
112
+ fi
113
+
114
+ # Unknown tool -- allow by default
115
+ exit 0
@@ -0,0 +1,97 @@
1
+ #!/bin/bash
2
+ # block-sensitive-files.sh
3
+ # PreToolUse hook — blocks Claude Code from reading sensitive files
4
+ # Exit code 2 = block the action, stderr message goes to Claude as feedback
5
+
6
+ set -euo pipefail
7
+
8
+ LOG_FILE="$HOME/.claude/guardrail-blocks.log"
9
+ HOOK_NAME="block-sensitive-files"
10
+
11
+ log_block() {
12
+ local reason="$1"
13
+ local detail="$2"
14
+ local timestamp
15
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
16
+ echo "{\"timestamp\":\"$timestamp\",\"hook\":\"$HOOK_NAME\",\"reason\":\"$reason\",\"detail\":\"$detail\",\"user\":\"$(whoami)\"}" >> "$LOG_FILE"
17
+ }
18
+
19
+ INPUT=$(cat)
20
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
21
+
22
+ # Get the file path depending on the tool
23
+ FILE_PATH=""
24
+ if [ "$TOOL_NAME" = "Read" ]; then
25
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
26
+ elif [ "$TOOL_NAME" = "Bash" ]; then
27
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
28
+ # Check if bash command references sensitive files
29
+ SENSITIVE_PATTERNS=(
30
+ '\.env'
31
+ 'credentials\.'
32
+ 'secret'
33
+ 'token'
34
+ '\.pem'
35
+ '\.key$'
36
+ '\.crt$'
37
+ '\.p12'
38
+ '\.pfx'
39
+ '\.keystore'
40
+ '\.jks'
41
+ '\.ssh/'
42
+ '\.aws/'
43
+ '\.azure/'
44
+ '\.gcloud/'
45
+ 'snowflake\.config'
46
+ 'profiles\.yml'
47
+ 'connections\.toml'
48
+ )
49
+ for pattern in "${SENSITIVE_PATTERNS[@]}"; do
50
+ if echo "$COMMAND" | grep -qiE "$pattern"; then
51
+ log_block "Matched pattern: $pattern" "$COMMAND"
52
+ echo "BLOCKED: Command references a sensitive file pattern ($pattern). Do not access credential files, .env files, or secret files. Ask the developer to provide the specific values you need." >&2
53
+ exit 2
54
+ fi
55
+ done
56
+ exit 0
57
+ fi
58
+
59
+ # For Read tool — check against blocked patterns
60
+ if [ -n "$FILE_PATH" ]; then
61
+ BLOCKED_PATTERNS=(
62
+ '\.env$'
63
+ '\.env\.'
64
+ 'credentials\.json'
65
+ 'credentials\.yaml'
66
+ 'credentials\.toml'
67
+ 'secret'
68
+ 'token'
69
+ '\.pem$'
70
+ '\.key$'
71
+ '\.crt$'
72
+ '\.p12$'
73
+ '\.pfx$'
74
+ '\.keystore$'
75
+ '\.jks$'
76
+ '/\.ssh/'
77
+ '/\.aws/'
78
+ '/\.azure/'
79
+ '/\.gcloud/'
80
+ 'snowflake\.config'
81
+ 'profiles\.yml'
82
+ 'connections\.toml'
83
+ '/secrets/'
84
+ '/credentials/'
85
+ '/private/'
86
+ '/keys/'
87
+ )
88
+ for pattern in "${BLOCKED_PATTERNS[@]}"; do
89
+ if echo "$FILE_PATH" | grep -qiE "$pattern"; then
90
+ log_block "Matched pattern: $pattern" "$FILE_PATH"
91
+ echo "BLOCKED: Access to '$FILE_PATH' is denied by company security policy. This file matches a protected pattern ($pattern). Ask the developer to provide the specific non-sensitive values you need." >&2
92
+ exit 2
93
+ fi
94
+ done
95
+ fi
96
+
97
+ exit 0