Skip to content

Trivy image scanning in CI

CI/CD · Security
Studied

Problem

The team builds Docker images, pushes them to a registry and deploys to production. We learn about CVEs when a security researcher sends an email or an external scanner fires an alert in the morning. An image with a vulnerable openssl lives in production for weeks.

Solution

Trivy is "a comprehensive and versatile security scanner": it scans container images, filesystems, Git repositories and Kubernetes for known vulnerabilities (CVEs), IaC misconfigurations and more. Put it into the CI pipeline as a mandatory step between docker build and docker push. The build fails if HIGH/CRITICAL vulnerabilities with an available fix are found — this forces base images to be updated instead of ignored.

The scanner itself is also a dependency with access to CI secrets. Pin the action to a full commit SHA, the image to a digest, and set the Trivy version explicitly.

Trivy compromise, March 2026

On March 19, 2026 a malicious Trivy v0.69.4 was released, and on March 22 malicious images v0.69.5 and v0.69.6; 75 trivy-action tags and the setup-trivy tags were rewritten to commits that steal runner secrets. CI runs from March 19 to 23 are considered affected. If Trivy ran in CI by tag during that period, rotate every secret available to those jobs. Details: CSA, SafeDep.

Example

GitHub Actions:

# .github/workflows/build.yml
- name: Build image
  run: docker build -t myapp:${{ github.sha }} .

- name: Scan with Trivy
  # v0.36.0, pinned by SHA: a tag can be rewritten, a commit cannot
  uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
  with:
    version: v0.76.1            # explicit Trivy version, not latest
    image-ref: myapp:${{ github.sha }}
    severity: HIGH,CRITICAL
    ignore-unfixed: true        # no noise for CVEs without a patch
    exit-code: 1                # fail the build
    format: sarif
    output: trivy.sarif

- name: Upload SARIF
  if: always()
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: trivy.sarif     # visible in the GitHub Security tab

GitLab CI — scanner image pinned by digest:

# .gitlab-ci.yml
trivy:
  stage: test
  image:
    # index digest of 0.76.1 on Docker Hub as of 2026-10-29; when updating, get the new one:
    # docker buildx imagetools inspect aquasec/trivy:<version>
    name: aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c
    entrypoint: [""]
  script:
    - trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"

Locally before pushing:

trivy image --severity HIGH,CRITICAL --ignore-unfixed myapp:dev

When to use

  • Any repository with a Dockerfile whose image ends up in production.
  • Before releasing a base image — as a gate, not as a report.

Source

How to apply it yourself

  1. Add a Trivy step to CI with severity HIGH,CRITICAL and ignore-unfixed: true; the action pinned by SHA, the image by digest.
  2. First run: list the current CVEs in an issue, fix them or consciously ignore them via .trivyignore.
  3. Enable exit-code: 1 and make the job required for merge.
  4. When the first PR fails on a real CVE and the team fixes the base image — status Studied.