Add performance-optimizer subagent and dependency-check hook (#36)
* feat: add performance-optimizer subagent and dependency-check hook Agent-Logs-Url: https://github.com/khanhnkq/claude-howto/sessions/ef3fb01c-a9c0-466e-9ad2-f255f306add2 Co-authored-by: khanhnkq <180888435+khanhnkq@users.noreply.github.com> * fix(hooks): use basename for manifest matching in dependency-check.sh FILE=$1 receives an absolute path (e.g. /home/user/project/package.json). The case statement and all [[ "$FILE" == ... ]] guards matched bare filenames, so every invocation would hit the *) exit 0 branch and skip all vuln scans. Extract BASENAME=$(basename "$FILE") and use it for all pattern matching. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Luong NGUYEN <luongnv89@gmail.com>
This commit is contained in:
156
06-hooks/dependency-check.sh
Executable file
156
06-hooks/dependency-check.sh
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/bin/bash
|
||||
# Check for known vulnerabilities in dependencies after manifest files are modified.
|
||||
# Hook: PostToolUse:Write
|
||||
|
||||
FILE=$1
|
||||
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "Usage: $0 <file_path>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Use basename for matching — $1 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
|
||||
Reference in New Issue
Block a user