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.
- opsentry/VERSION +1 -0
- opsentry/__init__.py +5 -0
- opsentry/cli.py +295 -0
- opsentry/config/__init__.py +0 -0
- opsentry/config/generate.py +414 -0
- opsentry/config/hook_templates/block-dangerous-commands.sh.j2 +102 -0
- opsentry/config/hook_templates/block-data-exfiltration.sh.j2 +94 -0
- opsentry/config/hook_templates/block-environment-escape.sh.j2 +99 -0
- opsentry/config/hook_templates/block-git-commands.sh.j2 +46 -0
- opsentry/config/hook_templates/block-package-install.sh.j2 +95 -0
- opsentry/config/hook_templates/block-pii-leakage.sh.j2 +100 -0
- opsentry/config/hook_templates/block-scope-escape.sh.j2 +115 -0
- opsentry/config/hook_templates/block-sensitive-files.sh.j2 +97 -0
- opsentry/config/init_wizard.py +201 -0
- opsentry/config/rule_texts/01_sensitive_files.md +15 -0
- opsentry/config/rule_texts/02_client_data.md +10 -0
- opsentry/config/rule_texts/03_credentials.md +17 -0
- opsentry/config/rule_texts/04_sql_safety.md +33 -0
- opsentry/config/rule_texts/05_dangerous_code.md +29 -0
- opsentry/config/rule_texts/06_frontend_xss.md +9 -0
- opsentry/config/rule_texts/07_git_operations_block_all.md +13 -0
- opsentry/config/rule_texts/07_git_operations_read_only.md +18 -0
- opsentry/config/rule_texts/08_destructive_commands.md +12 -0
- opsentry/config/rule_texts/09_database_safety.md +17 -0
- opsentry/config/rule_texts/10_network_requests.md +10 -0
- opsentry/config/rule_texts/11_code_quality.md +13 -0
- opsentry/config/rule_texts/12_data_exfiltration.md +10 -0
- opsentry/config/rule_texts/13_package_install.md +9 -0
- opsentry/config/rule_texts/14_scope_boundaries.md +8 -0
- opsentry/config/rule_texts/15_env_isolation.md +9 -0
- opsentry/config/rule_texts/16_resource_limits.md +8 -0
- opsentry/config/rule_texts/17_compliance_privacy.md +9 -0
- opsentry/config/rule_texts/18_when_in_doubt.md +10 -0
- opsentry/config/schema.py +142 -0
- opsentry/config/templates/generic.yaml +84 -0
- opsentry/deployment/.claude/commands/code-health.md +42 -0
- opsentry/deployment/.claude/commands/governance-check.md +29 -0
- opsentry/deployment/.claude/commands/security-audit.md +41 -0
- opsentry/deployment/baseline.py +440 -0
- opsentry/deployment/blocklog_audit.py +497 -0
- opsentry/deployment/claude/CLAUDE.md +300 -0
- opsentry/deployment/claude/hooks/block-dangerous-commands.sh +106 -0
- opsentry/deployment/claude/hooks/block-data-exfiltration.sh +99 -0
- opsentry/deployment/claude/hooks/block-environment-escape.sh +96 -0
- opsentry/deployment/claude/hooks/block-git-commands.sh +37 -0
- opsentry/deployment/claude/hooks/block-package-install.sh +103 -0
- opsentry/deployment/claude/hooks/block-pii-leakage.sh +86 -0
- opsentry/deployment/claude/hooks/block-scope-escape.sh +210 -0
- opsentry/deployment/claude/hooks/block-sensitive-files.sh +97 -0
- opsentry/deployment/claude/settings.json +72 -0
- opsentry/deployment/install.sh +343 -0
- opsentry/deployment/patrol.sh +266 -0
- opsentry/deployment/test.sh +728 -0
- opsentry/deployment/update.sh +19 -0
- opsentry/deployment/verify.sh +201 -0
- opsentry-1.8.0.dist-info/METADATA +158 -0
- opsentry-1.8.0.dist-info/RECORD +61 -0
- opsentry-1.8.0.dist-info/WHEEL +5 -0
- opsentry-1.8.0.dist-info/entry_points.txt +2 -0
- opsentry-1.8.0.dist-info/licenses/LICENSE +190 -0
- 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
|