← Recipes

阅读中文版本

Fail the Pipeline on Fixable Image Vulnerabilities

Scan the built container image, ignore unfixed findings, and block promotion on high or critical vulnerabilities that already have a patch.

~25 minsecuritycontainersci

Task

Stop vulnerable base images from reaching production without drowning the team in findings that cannot be fixed yet.

When to use this

A base image tag is rebuilt upstream with a critical OpenSSL CVE. The pipeline pushes it, and an external scan finds the vulnerability two weeks after release.

Prerequisites

  • Trivy 0.50 or newer in CI
  • An image pushed by tag or digest from the same job
  • A documented severity and exception policy

Steps

1. Scan the image, ignore findings without a fix, and fail on high or critical severity.

trivy image --ignore-unfixed --severity HIGH,CRITICAL --exit-code 1 --format table ghcr.io/acme/api:$GITHUB_SHA

Expected output: Exit code 1 with a table listing each CVE, package, installed version, and fixed version.

2. Cache the vulnerability database so the scan does not download it on every job.

- uses: actions/cache@v4
  with:
    path: ~/.cache/trivy
    key: trivy-db-${{ github.run_id }}
    restore-keys: trivy-db-

Expected output: Repeated scans reuse the database and finish in seconds instead of minutes.

3. Emit SARIF so findings appear in the repository security tab.

trivy image --ignore-unfixed --severity HIGH,CRITICAL --format sarif -o trivy.sarif ghcr.io/acme/api:$GITHUB_SHA

Expected output: trivy.sarif uploads successfully and findings reference the image digest as their location.

4. Re-scan the exact digest that is about to be deployed, not the moving tag.

trivy image --exit-code 1 --severity CRITICAL ghcr.io/acme/api@sha256:6f1c2a9d4b7e8f0a1c3d5e7f9b2a4c6d8e0f1a3b5c7d9e1f3a5b7c9d1e3f5a7b

Expected output: The scan covers the exact bytes scheduled for deployment and fails on critical findings.

Troubleshooting

Problem

The first scan takes several minutes

Solution

The vulnerability database download dominates; cache it and refresh on a schedule instead of on every job.

Problem

Findings appear only after the deploy

Solution

Scan before promotion, and stop the pipeline in the same job that pushed the image rather than in a later workflow.

Limitations

  • Image scanners cannot see logic flaws and may miss language packages installed outside the OS package database.
  • ignore-unfixed hides real risk; review the unfixed list in a scheduled report.

Scan the artifact you will run

Scanning a Dockerfile or a lockfile is not the same as scanning the image. The runtime image contains OS packages from the base layer, and those are where base-image CVEs appear. Scan the pushed image, and scan the digest rather than the tag, because a tag can be moved between the scan and the deploy. Put the scan in the same job that builds and pushes, before any promotion step, so a failure stops the release instead of being reported afterwards.

trivy image --ignore-unfixed --severity HIGH,CRITICAL \
  --exit-code 1 ghcr.io/acme/api@sha256:6f1c2a9d4b7e8f0a1c3d5e7f9b2a4c6d8e0f1a3b5c7d9e1f3a5b7c9d1e3f5a7b

Make the gate survivable

A gate that fails on every unfixed finding is turned off within a week. --ignore-unfixed keeps the gate focused on findings the team can actually resolve, and the unfixed list moves to a scheduled report where it can be reviewed without blocking delivery. Keep the exception list small and time-boxed: an exception should name the CVE, the reason, and an expiry, and it should be removed automatically when the expiry passes. Anything broader becomes permanent silence.

Verify both directions

Prove the gate can fail before trusting it: temporarily deploy an older base image with a known fixable CVE and watch the pipeline stop. Then verify the SARIF output maps findings to the image so remediation work has a clear owner. Finally, rebuild on a schedule even without code changes, because a new CVE against an unchanged image still needs a new pipeline run to be detected.