claude-code-ultimate-guide/examples/hooks/bash/output-secrets-scanner.sh
Florian BRUNIAUX 34b2ca7200 feat(security): add security hardening guide and hooks v3.6.0
- Add guide/security-hardening.md (~10K) covering:
  - MCP vetting workflow with CVE-2025-53109/53110, 54135, 54136
  - Prompt injection evasion techniques (Unicode, ANSI, null bytes)
  - Secret detection tool comparison (Gitleaks, TruffleHog, GitGuardian)
  - Incident response procedures

- Add 3 new security hooks:
  - unicode-injection-scanner.sh: zero-width, RTL, ANSI escape detection
  - repo-integrity-scanner.sh: scan README/package.json for injection
  - mcp-config-integrity.sh: verify MCP config hash

- Update existing hooks:
  - prompt-injection-detector.sh: +ANSI, +null bytes, +nested cmd
  - output-secrets-scanner.sh: +env leakage, +generic tokens

- Update cross-references in ultimate-guide.md (§7.4, §8.6)
- Move MCP Security Hardening to Done in IDEAS.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 07:39:53 +01:00

106 lines
3.8 KiB
Bash

#!/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