mirror of
https://github.com/n8n-io/n8n.git
synced 2026-07-28 03:24:59 +02:00
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
161 lines
6.8 KiB
YAML
161 lines
6.8 KiB
YAML
name: Security - Scan Docker Image With Trivy
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
image_ref:
|
|
description: 'Full image reference to scan e.g. ghcr.io/n8n-io/n8n:latest'
|
|
required: true
|
|
default: 'ghcr.io/n8n-io/n8n:latest'
|
|
workflow_call:
|
|
inputs:
|
|
image_ref:
|
|
type: string
|
|
description: 'Full image reference to scan e.g. ghcr.io/n8n-io/n8n:latest'
|
|
required: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
security_scan:
|
|
name: Security - Scan Docker Image With Trivy
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout for VEX file
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
sparse-checkout: |
|
|
security/vex.openvex.json
|
|
security/trivy.yaml
|
|
security/trivy-ignore-policy.rego
|
|
.github/scripts/retry.mjs
|
|
sparse-checkout-cone-mode: false
|
|
|
|
- name: Pull Docker image with retry
|
|
env:
|
|
IMAGE_REF: ${{ inputs.image_ref }}
|
|
run: node .github/scripts/retry.mjs --attempts 4 --delay 15 -- docker pull "$IMAGE_REF"
|
|
|
|
- name: Run Trivy vulnerability scanner
|
|
uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # v0.34.1
|
|
id: trivy_scan
|
|
with:
|
|
image-ref: ${{ inputs.image_ref }}
|
|
version: 'v0.69.2'
|
|
format: 'json'
|
|
output: 'trivy-results.json'
|
|
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
|
|
ignore-unfixed: false
|
|
exit-code: '0'
|
|
trivy-config: 'security/trivy.yaml'
|
|
|
|
- name: Calculate vulnerability counts
|
|
id: process_results
|
|
run: |
|
|
if [ ! -s trivy-results.json ] || [ "$(jq '.Results | length' trivy-results.json)" -eq 0 ]; then
|
|
echo "No vulnerabilities found."
|
|
echo "vulnerabilities_found=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
# Calculate counts by severity
|
|
CRITICAL_COUNT=$(jq '([.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length)' trivy-results.json)
|
|
HIGH_COUNT=$(jq '([.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length)' trivy-results.json)
|
|
MEDIUM_COUNT=$(jq '([.Results[]?.Vulnerabilities[]? | select(.Severity == "MEDIUM")] | length)' trivy-results.json)
|
|
LOW_COUNT=$(jq '([.Results[]?.Vulnerabilities[]? | select(.Severity == "LOW")] | length)' trivy-results.json)
|
|
TOTAL_VULNS=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT))
|
|
|
|
# Get unique CVE count
|
|
UNIQUE_CVES=$(jq -r '[.Results[]?.Vulnerabilities[]?.VulnerabilityID] | unique | length' trivy-results.json)
|
|
|
|
# Get affected packages count
|
|
AFFECTED_PACKAGES=$(jq -r '[.Results[]?.Vulnerabilities[]? | .PkgName] | unique | length' trivy-results.json)
|
|
|
|
{
|
|
echo "vulnerabilities_found=$( [ "$TOTAL_VULNS" -gt 0 ] && echo 'true' || echo 'false' )"
|
|
echo "total_count=$TOTAL_VULNS"
|
|
echo "critical_count=$CRITICAL_COUNT"
|
|
echo "high_count=$HIGH_COUNT"
|
|
echo "medium_count=$MEDIUM_COUNT"
|
|
echo "low_count=$LOW_COUNT"
|
|
echo "unique_cves=$UNIQUE_CVES"
|
|
echo "affected_packages=$AFFECTED_PACKAGES"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Generate GitHub Job Summary
|
|
if: always()
|
|
env:
|
|
IMAGE_REF: ${{ inputs.image_ref }}
|
|
run: |
|
|
{
|
|
echo "# 🛡️ Trivy Security Scan Results"
|
|
echo ""
|
|
echo "**Image:** \`$IMAGE_REF\`"
|
|
echo "**Scan Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
|
echo ""
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
if [ ! -s trivy-results.json ]; then
|
|
{
|
|
echo "⚠️ **Scan did not produce results.** Check the 'Run Trivy vulnerability scanner' step for errors."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
elif [ "${{ steps.process_results.outputs.vulnerabilities_found }}" == "false" ]; then
|
|
{
|
|
echo "✅ **No vulnerabilities found!**"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
{
|
|
echo "## 📊 Summary"
|
|
echo "| Metric | Count |"
|
|
echo "|--------|-------|"
|
|
echo "| 🔴 Critical Vulnerabilities | ${{ steps.process_results.outputs.critical_count }} |"
|
|
echo "| 🟠 High Vulnerabilities | ${{ steps.process_results.outputs.high_count }} |"
|
|
echo "| 🟡 Medium Vulnerabilities | ${{ steps.process_results.outputs.medium_count }} |"
|
|
echo "| 🟡 Low Vulnerabilities | ${{ steps.process_results.outputs.low_count }} |"
|
|
echo "| 📋 Unique CVEs | ${{ steps.process_results.outputs.unique_cves }} |"
|
|
echo "| 📦 Affected Packages | ${{ steps.process_results.outputs.affected_packages }} |"
|
|
echo ""
|
|
echo "## 🚨 Top Vulnerabilities"
|
|
echo ""
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
{
|
|
# Generate detailed vulnerability table
|
|
jq -r --arg image_ref "$IMAGE_REF" '
|
|
# Collect all vulnerabilities
|
|
[.Results[] | select(.Vulnerabilities != null) | .Vulnerabilities[]] |
|
|
# Group by CVE ID to avoid duplicates
|
|
group_by(.VulnerabilityID) |
|
|
map({
|
|
cve: .[0].VulnerabilityID,
|
|
severity: .[0].Severity,
|
|
cvss: (.[0].CVSS.nvd.V3Score // "N/A"),
|
|
cvss_sort: (.[0].CVSS.nvd.V3Score // 0),
|
|
packages: [.[] | "\(.PkgName)@\(.InstalledVersion)"] | unique | join(", "),
|
|
fixed: (.[0].FixedVersion // "No fix available"),
|
|
description: (.[0].Description // "No description available") | split("\n")[0] | .[0:150]
|
|
}) |
|
|
# Sort by severity (CRITICAL, HIGH, MEDIUM, LOW) and CVSS score
|
|
sort_by(
|
|
if .severity == "CRITICAL" then 0
|
|
elif .severity == "HIGH" then 1
|
|
elif .severity == "MEDIUM" then 2
|
|
elif .severity == "LOW" then 3
|
|
else 4 end,
|
|
-.cvss_sort
|
|
) |
|
|
# Take top 15
|
|
.[:15] |
|
|
# Generate markdown table
|
|
"| CVE | Severity | CVSS | Package(s) | Fix Version | Description |",
|
|
"|-----|----------|------|------------|-------------|-------------|",
|
|
(.[] | "| [\(.cve)](https://nvd.nist.gov/vuln/detail/\(.cve)) | \(.severity) | \(.cvss) | `\(.packages)` | `\(.fixed)` | \(.description) |")
|
|
' trivy-results.json
|
|
|
|
echo ""
|
|
echo "---"
|
|
echo "🔍 **View detailed logs above for full analysis**"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|