Loading...
Comprehensive Docker image vulnerability scanning with layer analysis, base image recommendations, and security best practices enforcement
{
"hookConfig": {
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/docker-image-security-scanner.sh",
"matchers": [
"write",
"edit"
]
}
}
},
"scriptContent": "#!/usr/bin/env bash\n\n# Read the tool input from stdin\nINPUT=$(cat)\nTOOL_NAME=$(echo \"$INPUT\" | jq -r '.tool_name')\nFILE_PATH=$(echo \"$INPUT\" | jq -r '.tool_input.file_path // .tool_input.path // \"\"')\n\nif [ -z \"$FILE_PATH\" ]; then\n exit 0\nfi\n\n# Configuration\nSECURITY_REPORT=\".claude/reports/docker-security-$(date +%Y%m%d).txt\"\nSEVERITY_THRESHOLD=${DOCKER_SCAN_SEVERITY:-HIGH}\nSCAN_ENABLED=${DOCKER_SECURITY_SCAN:-true}\n\nmkdir -p \"$(dirname \"$SECURITY_REPORT\")\"\n\n# Function to check if file is a Dockerfile\nis_dockerfile() {\n local file=$1\n [[ \"$file\" == *Dockerfile* ]] || [[ \"$file\" == *.dockerfile ]]\n}\n\n# Function to analyze Dockerfile for security issues\nanalyze_dockerfile_security() {\n local dockerfile=$1\n \n echo \"đ Analyzing Dockerfile security practices: $dockerfile\" >&2\n echo \"\" >> \"$SECURITY_REPORT\"\n echo \"Dockerfile Security Analysis - $(date)\" >> \"$SECURITY_REPORT\"\n echo \"========================================\" >> \"$SECURITY_REPORT\"\n echo \"File: $dockerfile\" >> \"$SECURITY_REPORT\"\n echo \"\" >> \"$SECURITY_REPORT\"\n \n local issues_found=0\n \n # Check for non-root user\n if ! grep -i \"^USER\" \"$dockerfile\" >/dev/null 2>&1; then\n echo \"â ī¸ WARNING: No USER directive found (running as root)\" >&2\n echo \"[SECURITY] Missing USER directive - container runs as root\" >> \"$SECURITY_REPORT\"\n issues_found=$((issues_found + 1))\n fi\n \n # Check for version pinning\n if grep -i \"^FROM.*:latest\" \"$dockerfile\" >/dev/null 2>&1; then\n echo \"â ī¸ WARNING: Using :latest tag (not reproducible)\" >&2\n echo \"[SECURITY] Base image uses :latest tag instead of pinned version\" >> \"$SECURITY_REPORT\"\n issues_found=$((issues_found + 1))\n fi\n \n # Check for COPY with broad wildcards\n if grep -i \"COPY . \" \"$dockerfile\" >/dev/null 2>&1; then\n echo \"đĄ INFO: COPY . detected - ensure .dockerignore excludes secrets\" >&2\n echo \"[INFO] Broad COPY directive - verify .dockerignore configuration\" >> \"$SECURITY_REPORT\"\n fi\n \n # Check for hardcoded secrets\n if grep -iE \"PASSWORD|SECRET|TOKEN|KEY.*=\" \"$dockerfile\" >/dev/null 2>&1; then\n echo \"đ¨ CRITICAL: Potential hardcoded secrets detected!\" >&2\n echo \"[CRITICAL] Hardcoded credentials found - use build args or secrets\" >> \"$SECURITY_REPORT\"\n issues_found=$((issues_found + 1))\n fi\n \n # Check for HEALTHCHECK\n if ! grep -i \"^HEALTHCHECK\" \"$dockerfile\" >/dev/null 2>&1; then\n echo \"đĄ INFO: No HEALTHCHECK directive (recommended for production)\" >&2\n echo \"[INFO] Missing HEALTHCHECK - consider adding for production readiness\" >> \"$SECURITY_REPORT\"\n fi\n \n # Check for minimal base images\n if grep -iE \"FROM.*ubuntu|FROM.*debian\" \"$dockerfile\" >/dev/null 2>&1; then\n echo \"đĄ INFO: Consider using alpine or distroless for smaller attack surface\" >&2\n echo \"[INFO] Full OS base image - consider alpine or distroless alternatives\" >> \"$SECURITY_REPORT\"\n fi\n \n echo \"\" >> \"$SECURITY_REPORT\"\n echo \"Issues found: $issues_found\" >> \"$SECURITY_REPORT\"\n \n return $issues_found\n}\n\n# Function to scan image with Trivy\nscan_with_trivy() {\n local image=$1\n \n if ! command -v trivy &> /dev/null; then\n echo \"đĄ Install Trivy for comprehensive vulnerability scanning\" >&2\n echo \" brew install trivy (macOS)\" >&2\n echo \" apt install trivy (Debian/Ubuntu)\" >&2\n return\n fi\n \n echo \"đ Scanning image with Trivy: $image\" >&2\n \n echo \"\" >> \"$SECURITY_REPORT\"\n echo \"Trivy Vulnerability Scan\" >> \"$SECURITY_REPORT\"\n echo \"========================\" >> \"$SECURITY_REPORT\"\n \n # Run Trivy scan\n trivy image --severity \"$SEVERITY_THRESHOLD\",CRITICAL \\\n --format json \"$image\" 2>/dev/null | \\\n jq -r '.Results[]? | .Vulnerabilities[]? | \"\\(.VulnerabilityID): \\(.Severity) - \\(.Title)\"' 2>/dev/null | \\\n head -20 >> \"$SECURITY_REPORT\" || \\\n echo \"â
No vulnerabilities found at $SEVERITY_THRESHOLD or higher severity\" >> \"$SECURITY_REPORT\"\n \n # Get summary\n local vuln_count=$(trivy image --severity CRITICAL,HIGH --format json \"$image\" 2>/dev/null | \\\n jq '[.Results[]?.Vulnerabilities[]?] | length' 2>/dev/null || echo \"0\")\n \n if [ \"$vuln_count\" -gt 0 ]; then\n echo \"\" >&2\n echo \"đ¨ Found $vuln_count HIGH/CRITICAL vulnerabilities in $image\" >&2\n echo \"đĄ Review full report: $SECURITY_REPORT\" >&2\n else\n echo \"â
No critical vulnerabilities detected\" >&2\n fi\n}\n\n# Function to scan with Docker Scout\nscan_with_docker_scout() {\n local image=$1\n \n if ! docker scout version &> /dev/null 2>&1; then\n echo \"đĄ Docker Scout available in Docker Desktop 4.17+\" >&2\n return\n fi\n \n echo \"đ Scanning with Docker Scout: $image\" >&2\n \n echo \"\" >> \"$SECURITY_REPORT\"\n echo \"Docker Scout Analysis\" >> \"$SECURITY_REPORT\"\n echo \"=====================\" >> \"$SECURITY_REPORT\"\n \n docker scout cves \"$image\" --format json 2>/dev/null | \\\n jq -r '.vulnerabilities[] | \"\\(.id): \\(.severity) - \\(.packageName)\"' 2>/dev/null | \\\n head -15 >> \"$SECURITY_REPORT\" || \\\n echo \"â
Scout scan complete\" >> \"$SECURITY_REPORT\"\n}\n\n# Main execution\nif is_dockerfile \"$FILE_PATH\"; then\n echo \"đŗ Dockerfile detected: $FILE_PATH\" >&2\n \n if [ \"$SCAN_ENABLED\" != \"true\" ]; then\n echo \"âšī¸ Security scanning disabled (DOCKER_SECURITY_SCAN=false)\" >&2\n exit 0\n fi\n \n # Analyze Dockerfile best practices\n analyze_dockerfile_security \"$FILE_PATH\"\n \n # Try to determine image name\n IMAGE_NAME=$(grep -i \"^FROM\" \"$FILE_PATH\" | tail -1 | awk '{print $2}')\n \n if [ -n \"$IMAGE_NAME\" ]; then\n echo \"đĻ Base image: $IMAGE_NAME\" >&2\n \n # Check if Docker is available and daemon is running\n if command -v docker &> /dev/null && docker info &> /dev/null 2>&1; then\n # Pull image if not present\n if ! docker image inspect \"$IMAGE_NAME\" &> /dev/null; then\n echo \"đĨ Pulling base image for scanning...\" >&2\n docker pull \"$IMAGE_NAME\" >&2 2>/dev/null || \\\n echo \"â ī¸ Could not pull image for scanning\" >&2\n fi\n \n # Run security scans\n scan_with_trivy \"$IMAGE_NAME\"\n scan_with_docker_scout \"$IMAGE_NAME\"\n else\n echo \"â ī¸ Docker daemon not running - cannot scan images\" >&2\n fi\n fi\n \n # Display security best practices\n echo \"\" >&2\n echo \"đĄī¸ Docker Security Best Practices:\" >&2\n echo \" âĸ Use specific version tags, not :latest\" >&2\n echo \" âĸ Run containers as non-root user (USER directive)\" >&2\n echo \" âĸ Use multi-stage builds to minimize image size\" >&2\n echo \" âĸ Scan images regularly with Trivy or Docker Scout\" >&2\n echo \" âĸ Keep base images updated and prefer minimal bases\" >&2\n echo \" âĸ Never include secrets in images (use build secrets)\" >&2\n \n if [ -s \"$SECURITY_REPORT\" ]; then\n echo \"\" >&2\n echo \"đ Full security report: $SECURITY_REPORT\" >&2\n fi\nfi\n\nexit 0"
}
.claude/hooks/
~/.claude/hooks/
{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/docker-image-security-scanner.sh",
"matchers": [
"write",
"edit"
]
}
}
}
#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Configuration
SECURITY_REPORT=".claude/reports/docker-security-$(date +%Y%m%d).txt"
SEVERITY_THRESHOLD=${DOCKER_SCAN_SEVERITY:-HIGH}
SCAN_ENABLED=${DOCKER_SECURITY_SCAN:-true}
mkdir -p "$(dirname "$SECURITY_REPORT")"
# Function to check if file is a Dockerfile
is_dockerfile() {
local file=$1
[[ "$file" == *Dockerfile* ]] || [[ "$file" == *.dockerfile ]]
}
# Function to analyze Dockerfile for security issues
analyze_dockerfile_security() {
local dockerfile=$1
echo "đ Analyzing Dockerfile security practices: $dockerfile" >&2
echo "" >> "$SECURITY_REPORT"
echo "Dockerfile Security Analysis - $(date)" >> "$SECURITY_REPORT"
echo "========================================" >> "$SECURITY_REPORT"
echo "File: $dockerfile" >> "$SECURITY_REPORT"
echo "" >> "$SECURITY_REPORT"
local issues_found=0
# Check for non-root user
if ! grep -i "^USER" "$dockerfile" >/dev/null 2>&1; then
echo "â ī¸ WARNING: No USER directive found (running as root)" >&2
echo "[SECURITY] Missing USER directive - container runs as root" >> "$SECURITY_REPORT"
issues_found=$((issues_found + 1))
fi
# Check for version pinning
if grep -i "^FROM.*:latest" "$dockerfile" >/dev/null 2>&1; then
echo "â ī¸ WARNING: Using :latest tag (not reproducible)" >&2
echo "[SECURITY] Base image uses :latest tag instead of pinned version" >> "$SECURITY_REPORT"
issues_found=$((issues_found + 1))
fi
# Check for COPY with broad wildcards
if grep -i "COPY . " "$dockerfile" >/dev/null 2>&1; then
echo "đĄ INFO: COPY . detected - ensure .dockerignore excludes secrets" >&2
echo "[INFO] Broad COPY directive - verify .dockerignore configuration" >> "$SECURITY_REPORT"
fi
# Check for hardcoded secrets
if grep -iE "PASSWORD|SECRET|TOKEN|KEY.*=" "$dockerfile" >/dev/null 2>&1; then
echo "đ¨ CRITICAL: Potential hardcoded secrets detected!" >&2
echo "[CRITICAL] Hardcoded credentials found - use build args or secrets" >> "$SECURITY_REPORT"
issues_found=$((issues_found + 1))
fi
# Check for HEALTHCHECK
if ! grep -i "^HEALTHCHECK" "$dockerfile" >/dev/null 2>&1; then
echo "đĄ INFO: No HEALTHCHECK directive (recommended for production)" >&2
echo "[INFO] Missing HEALTHCHECK - consider adding for production readiness" >> "$SECURITY_REPORT"
fi
# Check for minimal base images
if grep -iE "FROM.*ubuntu|FROM.*debian" "$dockerfile" >/dev/null 2>&1; then
echo "đĄ INFO: Consider using alpine or distroless for smaller attack surface" >&2
echo "[INFO] Full OS base image - consider alpine or distroless alternatives" >> "$SECURITY_REPORT"
fi
echo "" >> "$SECURITY_REPORT"
echo "Issues found: $issues_found" >> "$SECURITY_REPORT"
return $issues_found
}
# Function to scan image with Trivy
scan_with_trivy() {
local image=$1
if ! command -v trivy &> /dev/null; then
echo "đĄ Install Trivy for comprehensive vulnerability scanning" >&2
echo " brew install trivy (macOS)" >&2
echo " apt install trivy (Debian/Ubuntu)" >&2
return
fi
echo "đ Scanning image with Trivy: $image" >&2
echo "" >> "$SECURITY_REPORT"
echo "Trivy Vulnerability Scan" >> "$SECURITY_REPORT"
echo "========================" >> "$SECURITY_REPORT"
# Run Trivy scan
trivy image --severity "$SEVERITY_THRESHOLD",CRITICAL \
--format json "$image" 2>/dev/null | \
jq -r '.Results[]? | .Vulnerabilities[]? | "\(.VulnerabilityID): \(.Severity) - \(.Title)"' 2>/dev/null | \
head -20 >> "$SECURITY_REPORT" || \
echo "â
No vulnerabilities found at $SEVERITY_THRESHOLD or higher severity" >> "$SECURITY_REPORT"
# Get summary
local vuln_count=$(trivy image --severity CRITICAL,HIGH --format json "$image" 2>/dev/null | \
jq '[.Results[]?.Vulnerabilities[]?] | length' 2>/dev/null || echo "0")
if [ "$vuln_count" -gt 0 ]; then
echo "" >&2
echo "đ¨ Found $vuln_count HIGH/CRITICAL vulnerabilities in $image" >&2
echo "đĄ Review full report: $SECURITY_REPORT" >&2
else
echo "â
No critical vulnerabilities detected" >&2
fi
}
# Function to scan with Docker Scout
scan_with_docker_scout() {
local image=$1
if ! docker scout version &> /dev/null 2>&1; then
echo "đĄ Docker Scout available in Docker Desktop 4.17+" >&2
return
fi
echo "đ Scanning with Docker Scout: $image" >&2
echo "" >> "$SECURITY_REPORT"
echo "Docker Scout Analysis" >> "$SECURITY_REPORT"
echo "=====================" >> "$SECURITY_REPORT"
docker scout cves "$image" --format json 2>/dev/null | \
jq -r '.vulnerabilities[] | "\(.id): \(.severity) - \(.packageName)"' 2>/dev/null | \
head -15 >> "$SECURITY_REPORT" || \
echo "â
Scout scan complete" >> "$SECURITY_REPORT"
}
# Main execution
if is_dockerfile "$FILE_PATH"; then
echo "đŗ Dockerfile detected: $FILE_PATH" >&2
if [ "$SCAN_ENABLED" != "true" ]; then
echo "âšī¸ Security scanning disabled (DOCKER_SECURITY_SCAN=false)" >&2
exit 0
fi
# Analyze Dockerfile best practices
analyze_dockerfile_security "$FILE_PATH"
# Try to determine image name
IMAGE_NAME=$(grep -i "^FROM" "$FILE_PATH" | tail -1 | awk '{print $2}')
if [ -n "$IMAGE_NAME" ]; then
echo "đĻ Base image: $IMAGE_NAME" >&2
# Check if Docker is available and daemon is running
if command -v docker &> /dev/null && docker info &> /dev/null 2>&1; then
# Pull image if not present
if ! docker image inspect "$IMAGE_NAME" &> /dev/null; then
echo "đĨ Pulling base image for scanning..." >&2
docker pull "$IMAGE_NAME" >&2 2>/dev/null || \
echo "â ī¸ Could not pull image for scanning" >&2
fi
# Run security scans
scan_with_trivy "$IMAGE_NAME"
scan_with_docker_scout "$IMAGE_NAME"
else
echo "â ī¸ Docker daemon not running - cannot scan images" >&2
fi
fi
# Display security best practices
echo "" >&2
echo "đĄī¸ Docker Security Best Practices:" >&2
echo " âĸ Use specific version tags, not :latest" >&2
echo " âĸ Run containers as non-root user (USER directive)" >&2
echo " âĸ Use multi-stage builds to minimize image size" >&2
echo " âĸ Scan images regularly with Trivy or Docker Scout" >&2
echo " âĸ Keep base images updated and prefer minimal bases" >&2
echo " âĸ Never include secrets in images (use build secrets)" >&2
if [ -s "$SECURITY_REPORT" ]; then
echo "" >&2
echo "đ Full security report: $SECURITY_REPORT" >&2
fi
fi
exit 0
Trivy scan fails with database update errors
Update Trivy vulnerability DB: trivy image --download-db-only. Check network connectivity to ghcr.io registry. Use offline mode with cached DB: trivy --skip-update. Clear cache: rm -rf ~/.cache/trivy.
Hook detects Dockerfile but Docker daemon not accessible
Start Docker Desktop or dockerd service. Check DOCKER_HOST environment variable. Verify user permissions: sudo usermod -aG docker $USER. Test with docker info before running hook.
False positive warnings for multi-stage builds with root
Hook checks final stage only if multiple USER directives. Add USER in final stage even if earlier stages use root. Use comments to document why root needed in build stages.
Base image pull fails behind corporate proxy or firewall
Configure Docker proxy in daemon.json. Use internal registry mirror. Pre-pull images: docker pull before hook runs. Skip image scanning: DOCKER_SECURITY_SCAN=false.
Docker Scout shows different results than Trivy
Different vulnerability databases and update frequencies. Scout uses Docker's curated database. Trivy uses multiple sources. Cross-reference both for comprehensive coverage. Check scan timestamps.
Loading reviews...
Join our community of Claude power users. No spam, unsubscribe anytime.
Automated accessibility testing and compliance checking for web applications following WCAG guidelines
Automatically generates or updates API documentation when endpoint files are modified
Automatically formats code files after Claude writes or edits them using Prettier, Black, or other formatters