diff --git a/.github/actions/scan-dependencies/action.yaml b/.github/actions/scan-dependencies/action.yaml index 10f0ca5e..76af32fc 100644 --- a/.github/actions/scan-dependencies/action.yaml +++ b/.github/actions/scan-dependencies/action.yaml @@ -42,6 +42,13 @@ runs: run: | export BUILD_DATETIME=${{ inputs.build_datetime }} ./scripts/reports/scan-vulnerabilities.sh + - name: "Generate vulnerabilities summary" + shell: bash + run: | + ./scripts/reports/parse-vulnerabilities.sh vulnerabilities-repository-report.json | tee vulnerabilities-summary.md + if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + cat vulnerabilities-summary.md >> "$GITHUB_STEP_SUMMARY" + fi - name: "Compress vulnerabilities report" shell: bash run: zip vulnerabilities-repository-report.json.zip vulnerabilities-repository-report.json @@ -52,6 +59,23 @@ runs: name: vulnerabilities-repository-report.json.zip path: ./vulnerabilities-repository-report.json.zip retention-days: 21 + - name: "Upload vulnerabilities summary as an artefact" + if: ${{ !env.ACT }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: vulnerabilities-summary.md + path: ./vulnerabilities-summary.md + retention-days: 21 + - name: "Fail if Critical or High vulnerabilities found" + shell: bash + run: | + CRITICAL_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' vulnerabilities-repository-report.json) + HIGH_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' vulnerabilities-repository-report.json) + echo "Critical: $CRITICAL_COUNT, High: $HIGH_COUNT" + if [[ "$CRITICAL_COUNT" -gt 0 || "$HIGH_COUNT" -gt 0 ]]; then + echo "::error::Found $CRITICAL_COUNT Critical and $HIGH_COUNT High severity vulnerabilities" + exit 1 + fi - name: "Check prerequisites for sending the reports" shell: bash id: check diff --git a/scripts/reports/parse-vulnerabilities.sh b/scripts/reports/parse-vulnerabilities.sh new file mode 100755 index 00000000..21f57020 --- /dev/null +++ b/scripts/reports/parse-vulnerabilities.sh @@ -0,0 +1,107 @@ +#!/bin/bash +# +# WARNING: This file is managed via the repository template. +# Local changes may diverge from the template source of truth. +# +# Parse vulnerability report JSON and output a human-readable summary +# Usage: ./parse-vulnerabilities.sh +# + +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " + exit 1 +fi + +REPORT_FILE="$1" + +if [[ ! -f "$REPORT_FILE" ]]; then + echo "Error: File not found: $REPORT_FILE" + exit 1 +fi + +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed" + exit 1 +fi + +# Get counts by severity +count_unique_severity() { + local severity="$1" + + jq -r --arg sev "$severity" ' + [.matches[] | select(.vulnerability.severity == $sev) | { + id: .vulnerability.id, + package: .artifact.name, + version: .artifact.version + }] + | unique_by(.id + .package + .version) + | length + ' "$REPORT_FILE" +} + +echo "## Vulnerability Report Summary" +echo "" + +CRITICAL_COUNT=$(count_unique_severity "Critical") +HIGH_COUNT=$(count_unique_severity "High") +MEDIUM_COUNT=$(count_unique_severity "Medium") +LOW_COUNT=$(count_unique_severity "Low") +TOTAL=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT)) + +echo "**Total: $TOTAL vulnerabilities** ($CRITICAL_COUNT Critical, $HIGH_COUNT High, $MEDIUM_COUNT Medium, $LOW_COUNT Low)" +echo "" + +# Function to print vulnerabilities for a given severity +print_severity_section() { + local severity="$1" + local count="$2" + + if [[ "$count" -eq 0 ]]; then + return + fi + + echo "### $severity ($count)" + echo "" + echo "| Package | Language | Version | Fix | Description |" + echo "|---------|---------|---------|-----|-------------|" + + jq -r --arg sev "$severity" ' + [.matches[] | select(.vulnerability.severity == $sev) | { + id: .vulnerability.id, + severity: .vulnerability.severity, + package: .artifact.name, + language: .artifact.language, + version: .artifact.version, + fix: (.vulnerability.fix.versions[0] // "N/A"), + description: .vulnerability.description + }] + | unique_by(.id + .package + .version) + | sort_by(.package) + | .[] + | "| \(.package) | \(.language) | \(.version) | \(.fix) | \(.description[0:70])... |" + ' "$REPORT_FILE" + + echo "" +} + +print_severity_section "Critical" "$CRITICAL_COUNT" +print_severity_section "High" "$HIGH_COUNT" +print_severity_section "Medium" "$MEDIUM_COUNT" +print_severity_section "Low" "$LOW_COUNT" + +# Priority packages summary +echo "---" +echo "" +echo "### Priority Packages to Update" +echo "" + +jq -r ' + [.matches[] | select(.vulnerability.severity == "Critical" or .vulnerability.severity == "High") | .artifact.name] + | unique + | sort + | join(", ") +' "$REPORT_FILE" | fold -s -w 80 + +echo ""