#!/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}" # 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_-]*" # 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 ["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,}" ) 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