Files
claude-howto/06-hooks/security-scan.sh
T
Luong NGUYENandGitHub b9a973bf32 docs: accuracy pass against Claude Code v2.1.220 (#155)
Internal accuracy pass against v2.1.220 — no missing upstream features, but broken example code, disagreeing counts, and metadata drift.

Functional fixes: pre-commit.sh now exits 2 so it actually blocks; dependency-check.sh reads file_path from stdin JSON instead of $1; database-mcp.json uses ${DATABASE_URL}; broken fences repaired; three command templates had invalid skill names.

Factual corrections: /fork and /subtask unswapped and /subtask added; /fewer-permission-prompts; permissions.defaultMode; dontAsk/auto unreversed; 31 hook events verified name-by-name; subagent depth 3; skill precedence enterprise > project > personal; /output-style removed not deprecated; permissionDecision gained defer.

Follow-up review fixed defects the pass left behind: zh/vi headers claiming 31 events above 25-name lists, a surviving hardcoded DB credential in the MCP README examples, an unbalanced fence swallowing a metadata footer, and non-canonical tool names. All four translated CATALOG summary tables were recounted so their arithmetic holds.

Full detail in CHANGELOG.md under v2.1.220-r2.
2026-08-04 15:41:12 +07:00

79 lines
3.2 KiB
Bash

#!/bin/bash
# Security scan on file write
# Hook: PostToolUse (matcher: Write)
#
# Scans files for hardcoded secrets, API keys, and credentials.
# Outputs a non-blocking warning via additionalContext when issues are found.
#
# Compatible with: macOS, Linux, Windows (Git Bash)
# Read JSON input from stdin (Claude Code hook protocol)
INPUT=$(cat)
# Extract file_path using sed (compatible with all platforms including Windows Git Bash)
# Avoids grep -P (not available on Windows Git Bash) and python3 dependency
FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
exit 0
fi
# Skip binary files, vendor dirs, and build artifacts
case "$FILE_PATH" in
*.png|*.jpg|*.jpeg|*.gif|*.svg|*.ico|*.woff|*.woff2|*.ttf|*.eot) exit 0 ;;
*/node_modules/*|*/.git/*|*/dist/*|*/build/*) exit 0 ;;
esac
ISSUES=""
# Check for hardcoded passwords
# Handles both JSON format ("password": "value") and code format (password = 'value')
# Use \\n as separator — it is a valid JSON newline escape and passes through printf safely
if grep -qiE '"password"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Potential hardcoded password detected\\n"
elif grep -qiE '(password|passwd|pwd)[[:space:]]*=[[:space:]]*'"'"'[^'"'"']+'"'"'' "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Potential hardcoded password detected\\n"
fi
# Check for hardcoded API keys
if grep -qiE '"(api[_-]?key|apikey|access[_-]?token)"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Potential hardcoded API key detected\\n"
fi
# Check for hardcoded secrets and tokens
if grep -qiE '(secret|token)[[:space:]]*=[[:space:]]*['"'"'"][^'"'"'"]+['"'"'"]' "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Potential hardcoded secret or token detected\\n"
fi
# Check for private keys
if grep -q "BEGIN.*PRIVATE KEY" "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Private key detected\\n"
fi
# Check for AWS keys
if grep -qE "AKIA[0-9A-Z]{16}" "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: AWS access key detected\\n"
fi
# Scan with semgrep if available (stdout suppressed to avoid mixing with JSON output)
if command -v semgrep &> /dev/null; then
semgrep --config=auto "$FILE_PATH" --quiet >/dev/null 2>/dev/null
fi
# Scan with trufflehog if available (stdout suppressed to avoid mixing with JSON output)
if command -v trufflehog &> /dev/null; then
trufflehog filesystem "$FILE_PATH" --only-verified --quiet >/dev/null 2>/dev/null
fi
# If issues found, output as additionalContext (non-blocking warning)
# Use hookSpecificOutput format required by Claude Code PostToolUse protocol
if [ -n "$ISSUES" ]; then
# Escape file path for JSON (backslash and double-quote)
# ISSUES already uses \\n as separator (valid JSON escape) — only escape double-quotes
SAFE_PATH=$(printf '%s' "$FILE_PATH" | sed 's/\\/\\\\/g; s/"/\\"/g')
SAFE_ISSUES=$(printf '%s' "$ISSUES" | sed 's/"/\\"/g')
printf '{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "Security scan found issues in %s:\\n%sPlease review and use environment variables instead."}}' "$SAFE_PATH" "$SAFE_ISSUES"
fi
exit 0