Files
claude-howto/06-hooks/dependency-check.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

163 lines
6.0 KiB
Bash
Executable File

#!/bin/bash
# Check for known vulnerabilities in dependencies after manifest files are modified.
# Hook: PostToolUse (matcher: Write)
#
# Reads the target file path from stdin JSON (Claude Code hook protocol).
# Source: https://code.claude.com/docs/en/hooks
# Read JSON input from stdin (Claude Code hook protocol)
INPUT=$(cat)
# Extract file_path using sed (compatible with all platforms)
FILE=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$FILE" ]; then
exit 0
fi
# Use basename for matching — file_path may be an absolute path
BASENAME=$(basename "$FILE")
# Only run when a dependency manifest is written
case "$BASENAME" in
package.json|package-lock.json|yarn.lock|pnpm-lock.yaml| \
requirements.txt|Pipfile|Pipfile.lock|pyproject.toml| \
go.mod|go.sum| \
Cargo.toml|Cargo.lock| \
Gemfile|Gemfile.lock| \
composer.json|composer.lock| \
pom.xml|build.gradle|build.gradle.kts)
echo "📦 Dependency manifest updated: $FILE — scanning for vulnerabilities..."
;;
*)
exit 0
;;
esac
ISSUES_FOUND=0
# ── npm / yarn / pnpm ────────────────────────────────────────────────────────
if [[ "$BASENAME" == package*.json || "$BASENAME" == yarn.lock || "$BASENAME" == pnpm-lock.yaml ]]; then
if command -v npm &>/dev/null; then
echo "🔍 Running npm audit..."
if ! npm audit --audit-level=high --json 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
vulns = data.get('metadata', {}).get('vulnerabilities', {})
high = vulns.get('high', 0) + vulns.get('critical', 0)
if high:
print(f' ⚠️ {high} high/critical npm vulnerabilities found. Run: npm audit fix')
sys.exit(1)
" 2>/dev/null; then
ISSUES_FOUND=1
else
echo " ✅ No high/critical npm vulnerabilities"
fi
fi
if command -v yarn &>/dev/null && [[ "$BASENAME" == yarn.lock ]]; then
echo "🔍 Running yarn audit..."
if ! yarn audit --level high --json 2>/dev/null | \
grep -q '"type":"auditAdvisory"' 2>/dev/null; then
echo " ✅ No high yarn vulnerabilities"
else
echo " ⚠️ yarn audit found vulnerabilities. Run: yarn audit --level high"
ISSUES_FOUND=1
fi
fi
fi
# ── Python ───────────────────────────────────────────────────────────────────
if [[ "$BASENAME" == requirements.txt || "$BASENAME" == Pipfile* || "$BASENAME" == pyproject.toml ]]; then
if command -v pip-audit &>/dev/null; then
echo "🔍 Running pip-audit..."
if pip-audit --format=json 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
vulns = [d for d in data.get('dependencies', []) if d.get('vulns')]
if vulns:
for dep in vulns:
for v in dep['vulns']:
print(f' ⚠️ {dep[\"name\"]} {dep[\"version\"]}: {v[\"id\"]} — {v[\"fix_versions\"]}')
sys.exit(1)
" 2>/dev/null; then
echo " ✅ No Python vulnerabilities found"
else
ISSUES_FOUND=1
echo " Run: pip-audit for details"
fi
elif command -v safety &>/dev/null; then
echo "🔍 Running safety check..."
OUTPUT=$(safety check --short-report 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo " ✅ No Python vulnerabilities found"
elif echo "$OUTPUT" | grep -qiE "vulnerability|CVE|insecure"; then
echo "$OUTPUT"
ISSUES_FOUND=1
else
echo " ⚠️ safety check could not complete (network or config error)" >&2
fi
fi
fi
# ── Go ───────────────────────────────────────────────────────────────────────
if [[ "$BASENAME" == go.mod || "$BASENAME" == go.sum ]]; then
if command -v govulncheck &>/dev/null; then
echo "🔍 Running govulncheck..."
OUTPUT=$(govulncheck ./... 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo " ✅ No Go vulnerabilities found"
elif echo "$OUTPUT" | grep -q "Vulnerability #"; then
echo "$OUTPUT"
ISSUES_FOUND=1
else
echo " ⚠️ govulncheck could not complete: $OUTPUT" >&2
fi
fi
fi
# ── Rust ─────────────────────────────────────────────────────────────────────
if [[ "$BASENAME" == Cargo.toml || "$BASENAME" == Cargo.lock ]]; then
if command -v cargo-audit &>/dev/null; then
echo "🔍 Running cargo audit..."
if ! cargo audit 2>/dev/null; then
ISSUES_FOUND=1
else
echo " ✅ No Rust vulnerabilities found"
fi
fi
fi
# ── Ruby ─────────────────────────────────────────────────────────────────────
if [[ "$BASENAME" == Gemfile || "$BASENAME" == Gemfile.lock ]]; then
if command -v bundler-audit &>/dev/null; then
echo "🔍 Running bundler-audit..."
bundler-audit check --update 2>/dev/null || ISSUES_FOUND=1
fi
fi
# ── Generic fallback: trivy ──────────────────────────────────────────────────
if command -v trivy &>/dev/null; then
echo "🔍 Running trivy fs scan..."
if ! trivy fs --exit-code 1 --severity HIGH,CRITICAL --quiet . 2>/dev/null; then
ISSUES_FOUND=1
else
echo " ✅ trivy found no HIGH/CRITICAL issues"
fi
fi
if [ "$ISSUES_FOUND" -eq 0 ]; then
echo "✅ Dependency check passed — no vulnerabilities detected"
else
echo ""
echo "⚠️ Vulnerabilities detected. Review and update dependencies before committing."
echo " This hook is advisory only and will not block your workflow."
fi
# Always exit 0 — this hook warns but does not block
exit 0