name: Trivy Multi-Arch CVE Delta Comparison description: Downloads and verifies Trivy, extracts multi-platform SBOMs, and computes a consolidated set-based CVE delta report inputs: head-image: description: Head container image reference (e.g. host/server/gitea:git-12345678) required: true target-image: description: Target baseline container image reference (e.g. host/server/gitea:latest) required: true target-branch: description: Target branch name for delta heading (e.g. main) required: false default: main trivy-version: description: Trivy binary version to install and verify required: false default: 0.74.0 # trivy-version output-delta: description: Output markdown file for CVE delta report required: false default: cve-delta.md output-summary: description: Output markdown file for full vulnerability summary required: false default: cve-summary.md write-summary: description: Whether to append the generated reports to the job summary ($GITHUB_STEP_SUMMARY) required: false default: 'true' outputs: delta-report: description: Path to CVE delta markdown report value: ${{ inputs.output-delta }} summary-report: description: Path to full vulnerability markdown report value: ${{ inputs.output-summary }} runs: using: composite steps: - name: Prepare Cache Keys id: vars shell: bash run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" - name: Cache Trivy (Binary & Vulnerability DB) uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/trivy-bin ~/.cache/trivy key: trivy-${{ inputs.trivy-version }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.vars.outputs.date }} restore-keys: | trivy-${{ inputs.trivy-version }}-${{ runner.os }}-${{ runner.arch }}- - name: Install and Verify Trivy shell: bash run: | mkdir -p "$HOME/.cache/trivy-bin" "$HOME/.local/bin" echo "$HOME/.local/bin" >> "$GITHUB_PATH" ARCH=$(uname -m) case "$ARCH" in x86_64) T_ARCH="64bit" ;; aarch64|arm64) T_ARCH="ARM64" ;; *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac tarball="trivy_${{ inputs.trivy-version }}_Linux-${T_ARCH}.tar.gz" checksums_file="${{ github.action_path }}/checksums.txt" if [ -f "$HOME/.cache/trivy-bin/${tarball}" ] && (cd "$HOME/.cache/trivy-bin" && grep " ${tarball}$" "$checksums_file" | sha256sum -c -); then echo "Cached Trivy archive verified against lockfile." else echo "Archive missing or checksum mismatch; downloading..." curl -sfLO "https://github.com/aquasecurity/trivy/releases/download/v${{ inputs.trivy-version }}/${tarball}" --output-dir "$HOME/.cache/trivy-bin" (cd "$HOME/.cache/trivy-bin" && grep " ${tarball}$" "$checksums_file" | sha256sum -c -) fi tar -xzf "$HOME/.cache/trivy-bin/${tarball}" -C "$HOME/.local/bin" trivy chmod +x "$HOME/.local/bin/trivy" - name: Extract Multi-Platform SBOMs shell: bash run: | export PATH="$HOME/.local/bin:$PATH" mkdir -p sboms/head sboms/target cleanup_file() { local f="$1" if [ ! -s "$f" ] || [ "$(cat "$f" 2>/dev/null)" = "null" ]; then rm -f "$f" fi } # Extract platform SBOMs for head image echo "Extracting platform SBOMs for head image: ${{ inputs.head-image }}..." docker buildx imagetools inspect "${{ inputs.head-image }}" \ --format '{{ json (index .SBOM "linux/amd64").SPDX }}' > sboms/head/sbom-amd64.spdx.json 2>/dev/null || true cleanup_file sboms/head/sbom-amd64.spdx.json docker buildx imagetools inspect "${{ inputs.head-image }}" \ --format '{{ json (index .SBOM "linux/arm64").SPDX }}' > sboms/head/sbom-arm64.spdx.json 2>/dev/null || true cleanup_file sboms/head/sbom-arm64.spdx.json # If single-arch, extract root .SPDX if [ ! -f sboms/head/sbom-amd64.spdx.json ] && [ ! -f sboms/head/sbom-arm64.spdx.json ]; then docker buildx imagetools inspect "${{ inputs.head-image }}" \ --format '{{ json .SBOM.SPDX }}' > sboms/head/sbom-amd64.spdx.json 2>/dev/null || true cleanup_file sboms/head/sbom-amd64.spdx.json fi # Fallback to generating head SBOM with Trivy if attestation missing if [ ! -f sboms/head/sbom-amd64.spdx.json ] && [ ! -f sboms/head/sbom-arm64.spdx.json ]; then echo "Head image lacks SBOM attestation; generating with Trivy..." trivy image --format spdx-json --output sboms/head/sbom-amd64.spdx.json "${{ inputs.head-image }}" || true cleanup_file sboms/head/sbom-amd64.spdx.json fi # Extract platform SBOMs for target image echo "Extracting platform SBOMs for target image: ${{ inputs.target-image }}..." docker buildx imagetools inspect "${{ inputs.target-image }}" \ --format '{{ json (index .SBOM "linux/amd64").SPDX }}' > sboms/target/sbom-amd64.spdx.json 2>/dev/null || true cleanup_file sboms/target/sbom-amd64.spdx.json docker buildx imagetools inspect "${{ inputs.target-image }}" \ --format '{{ json (index .SBOM "linux/arm64").SPDX }}' > sboms/target/sbom-arm64.spdx.json 2>/dev/null || true cleanup_file sboms/target/sbom-arm64.spdx.json if [ ! -f sboms/target/sbom-amd64.spdx.json ] && [ ! -f sboms/target/sbom-arm64.spdx.json ]; then docker buildx imagetools inspect "${{ inputs.target-image }}" \ --format '{{ json .SBOM.SPDX }}' > sboms/target/sbom-amd64.spdx.json 2>/dev/null || true cleanup_file sboms/target/sbom-amd64.spdx.json fi # Fallback to generating target SBOM with Trivy if attestation missing if [ ! -f sboms/target/sbom-amd64.spdx.json ] && [ ! -f sboms/target/sbom-arm64.spdx.json ]; then echo "Target image lacks SBOM attestation; attempting to generate with Trivy..." trivy image --format spdx-json --output sboms/target/sbom-amd64.spdx.json "${{ inputs.target-image }}" || true cleanup_file sboms/target/sbom-amd64.spdx.json fi # Expose extracted head SBOMs to workspace root for workflow artifact uploads [ -f sboms/head/sbom-amd64.spdx.json ] && cp sboms/head/sbom-amd64.spdx.json sbom-amd64.spdx.json || true [ -f sboms/head/sbom-arm64.spdx.json ] && cp sboms/head/sbom-arm64.spdx.json sbom-arm64.spdx.json || true - name: Scan Multi-Platform SBOMs and Generate Delta shell: bash run: | export PATH="$HOME/.local/bin:$PATH" mkdir -p cve-reports/head cve-reports/target # Scan each available head SBOM for f in sboms/head/sbom-*.spdx.json; do [ -f "$f" ] || continue arch=$(basename "$f" .spdx.json | sed 's/sbom-//') echo "Scanning head SBOM for architecture: $arch" trivy sbom --format json --output "cve-reports/head/cve-${arch}.json" "$f" || true done # Scan each available target SBOM for f in sboms/target/sbom-*.spdx.json; do [ -f "$f" ] || continue arch=$(basename "$f" .spdx.json | sed 's/sbom-//') echo "Scanning target SBOM for architecture: $arch" trivy sbom --format json --output "cve-reports/target/cve-${arch}.json" "$f" || true done # Run multi-arch set-based diff node "${{ github.action_path }}/cve-diff.js" \ "cve-reports/target" \ "cve-reports/head" \ "${{ inputs.target-branch }}" > "${{ inputs.output-delta }}" || true # Generate full markdown summary report for primary platform primary_sbom="" if [ -f sboms/head/sbom-amd64.spdx.json ]; then primary_sbom="sboms/head/sbom-amd64.spdx.json" elif [ -f sboms/head/sbom-arm64.spdx.json ]; then primary_sbom="sboms/head/sbom-arm64.spdx.json" fi if [ -n "$primary_sbom" ]; then trivy sbom \ --format template \ --template "@${{ github.action_path }}/markdown.tpl" \ --output "${{ inputs.output-summary }}" \ "$primary_sbom" || true fi - name: Publish Reports to Step Summary shell: bash run: | if [ "${{ inputs.write-summary }}" = "true" ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then if [ -f "${{ inputs.output-delta }}" ]; then cat "${{ inputs.output-delta }}" >> "$GITHUB_STEP_SUMMARY" fi if [ -f "${{ inputs.output-summary }}" ]; then cat "${{ inputs.output-summary }}" >> "$GITHUB_STEP_SUMMARY" fi fi