-
Notifications
You must be signed in to change notification settings - Fork 287
Expand file tree
/
Copy pathoutput-secrets-scanner.sh
More file actions
106 lines (90 loc) · 3.85 KB
/
output-secrets-scanner.sh
File metadata and controls
106 lines (90 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/bin/bash
# =============================================================================
# Output Secrets Scanner Hook
# =============================================================================
# Event: PostToolUse (runs after each tool execution)
# Purpose: Detect secrets that might leak in tool outputs
#
# This complements security-check.sh (which scans inputs). This hook scans
# outputs to catch secrets that Claude might inadvertently expose.
#
# Installation:
# Add to .claude/settings.json:
# {
# "hooks": {
# "PostToolUse": [{
# "matcher": "",
# "hooks": ["bash examples/hooks/bash/output-secrets-scanner.sh"]
# }]
# }
# }
#
# What it detects:
# - API keys (OpenAI, Anthropic, AWS, GCP, Azure, Stripe, etc.)
# - Private keys and certificates
# - Database connection strings with passwords
# - GitHub/GitLab tokens
# - JWT tokens
# =============================================================================
set -euo pipefail
# Read the hook input from stdin
INPUT=$(cat)
# Extract tool output from JSON (handle both formats)
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // .output // ""' 2>/dev/null || echo "")
# If no output or empty, exit cleanly
if [[ -z "$TOOL_OUTPUT" ]]; then
exit 0
fi
# Secret patterns to detect
declare -A SECRET_PATTERNS=(
# API Keys
["OpenAI API Key"]="sk-[a-zA-Z0-9]{20,}"
["Anthropic API Key"]="sk-ant-[a-zA-Z0-9]{20,}"
["AWS Access Key"]="AKIA[0-9A-Z]{16}"
["AWS Secret Key"]="[0-9a-zA-Z/+]{40}"
["GCP API Key"]="AIza[0-9A-Za-z_-]{35}"
["Azure Key"]="[a-zA-Z0-9]{32,}"
["Stripe Key"]="(sk|pk)_(live|test)_[0-9a-zA-Z]{24,}"
["Twilio Key"]="SK[a-f0-9]{32}"
["SendGrid Key"]="SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}"
["Slack Token"]="xox[baprs]-[0-9a-zA-Z-]{10,}"
["Discord Token"]="[MN][A-Za-z0-9]{23,}\.[A-Za-z0-9-_]{6}\.[A-Za-z0-9-_]{27}"
# Tokens
["GitHub Token"]="(ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,}"
["GitLab Token"]="glpat-[a-zA-Z0-9_-]{20,}"
["NPM Token"]="npm_[a-zA-Z0-9]{36}"
["PyPI Token"]="pypi-[a-zA-Z0-9_-]{50,}"
["JWT Token"]="eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*"
["Heroku API Key"]="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
# Private Keys
["Private Key"]="-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"
["PGP Private Key"]="-----BEGIN PGP PRIVATE KEY BLOCK-----"
# Database
["Database URL with Password"]="(postgres|mysql|mongodb)://[^:]+:[^@]+@"
["Redis URL with Password"]="redis://:[^@]+@"
# Generic (58% of leaked secrets are "generic" - GitGuardian 2025)
["Generic API Key"]="(api[_-]?key|apikey|api[_-]?secret)['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_-]{20,}"
["Generic Secret"]="(secret|password|passwd|pwd)['\"]?\s*[:=]\s*['\"]?[^\s'\"]{8,}"
["Generic Token"]="(token|auth[_-]?token|access[_-]?token|bearer)['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_-]{20,}"
["Private Key Inline"]="['\"]?-----BEGIN[^-]+PRIVATE KEY-----"
# Environment Variable Leakage
["Env Dump Command"]="^(env|printenv|set)$"
["Proc Environ Access"]="/proc/self/environ|/proc/[0-9]+/environ"
)
DETECTED_SECRETS=()
# Check each pattern
for secret_type in "${!SECRET_PATTERNS[@]}"; do
pattern="${SECRET_PATTERNS[$secret_type]}"
if echo "$TOOL_OUTPUT" | grep -qiE "$pattern" 2>/dev/null; then
DETECTED_SECRETS+=("$secret_type")
fi
done
# If secrets detected, warn via systemMessage
if [[ ${#DETECTED_SECRETS[@]} -gt 0 ]]; then
SECRETS_LIST=$(printf ", %s" "${DETECTED_SECRETS[@]}")
SECRETS_LIST=${SECRETS_LIST:2} # Remove leading ", "
WARNING_MSG="SECRET LEAK WARNING: Potential secrets detected in output: $SECRETS_LIST. Do NOT commit or share this output. Consider using environment variables or a secrets manager."
echo "{\"systemMessage\": \"$WARNING_MSG\"}"
fi
# Always exit 0 (warn, don't block)
exit 0