Skip to content

Add opt-in Trivy container CVE scan to build-docker#112

Merged
dblane-digicatapult merged 2 commits into
mainfrom
ENG-314-container-scan
Jul 14, 2026
Merged

Add opt-in Trivy container CVE scan to build-docker#112
dblane-digicatapult merged 2 commits into
mainfrom
ENG-314-container-scan

Conversation

@dblane-digicatapult

@dblane-digicatapult dblane-digicatapult commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Checklist

  • Have you read Digital Catapult's Code of Conduct?
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.

PR Type

  • Bug Fix
  • Feature
  • Documentation Update

Linked tickets

ENG-314

High level description

Adds an opt-in container image CVE scan to build-docker.yml. When scan_container: true, the built linux/amd64 image is scanned with Trivy and the build fails on CRITICAL findings — closing the gap that private repos get no container CVE scanning and no repo gets it on a PR (the existing Docker Scout step is release-only, public-only, and report-only).

Detailed description

  • The build job exports the linux/amd64 image as a tarball artifact (reusing the buildx cache and the registry logins already performed there for private base images).
  • A new scan-image job runs least-privilege (permissions: contents: read, no secrets), downloads that artifact, and scans it with Trivy. It uploads a JSON report artifact and fails on scan_fail_severity (default CRITICAL, --ignore-unfixed so the gate stays actionable).
  • Runs on any event, so it gates both PRs and releases wherever build-docker runs.

Security / supply-chain:

  • Trivy runs from a digest-pinned image (aquasec/trivy:0.72.0@sha256:cffe3f…), deliberately not the aquasecurity/trivy-action or a mutable tag — that action was supply-chain compromised in March 2026 (tags force-pushed to malicious commits, credential exfiltration).
  • The scanner is sandboxed to the image tarball and runs in a job with no secrets, so a future scanner compromise cannot reach the build job's GITHUB_TOKEN/DockerHub/DHI credentials.
  • No GHAS/SARIF upload for the Trivy scan — report artifact only (per requirement).

Rollout safety: scan_container defaults to false, so merging this does not change behaviour for existing consumers or fail their CI on pre-existing CRITICALs. Repos opt in explicitly; once the fleet is triaged the default can be flipped to true for org-wide coverage.

Validated locally with the exact digest-pinned image and invocations: report step produces a valid JSON artifact; the gate passes on a clean image (node:24-alpine) and fails (exit 1) on a vulnerable one (python:3.9.1-slim-buster, CRITICALs listed). Wired into testbed-portal, veritable-cloudagent and hello-world via branch-pinned PRs.

Describe alternatives you've considered

A standalone scan-container.yml that builds its own image (decoupled, opt-in, covers bespoke-build repos) — viable, but folding into build-docker avoids a duplicate build and applies everywhere build-docker is used. spec-rag-api uses a bespoke inline build on PRs so it is not covered until it migrates onto the shared build-docker.

Operational impact

Additive and off by default. When enabled, adds an amd64 image export to the build job and one scan-image job. No change to the push/release paths or the existing Docker Scout step.

Additional context

Container scanner selection and the digest-pinned/least-privilege delivery are documented on ENG-314. SHA-pinning all third-party actions org-wide is tracked separately (overlaps ENG-312 / ENG-309).

When scan_container is set, the build job exports the linux/amd64 image
as a tarball artifact and a separate least-privilege scan-image job
(contents: read, no secrets) scans it with a digest-pinned Trivy image,
uploads a JSON report artifact, and fails on scan_fail_severity (default
CRITICAL).

Trivy runs from a pinned digest, not the aquasecurity/trivy-action which
was supply-chain compromised in March 2026; the scanner is sandboxed to
the image tarball so it never sees the build job's registry credentials.
No GHAS/SARIF upload for the Trivy scan - report artifact only. Defaults
to off so enabling it does not break existing consumers' CI.

ENG-314
The report was filtered to CRITICAL,HIGH + fixed-only, so on clean images
it showed ~0 vulnerabilities and hid MEDIUM/LOW and unfixed findings. Now
run one comprehensive scan (all severities, fixed and unfixed) to the JSON
report artifact and the log, and compute the gate from that report with jq
(fail on scan_fail_severity, respecting scan_ignore_unfixed). Drops the
scan_report_severity input; the report is always complete.

ENG-314
@dblane-digicatapult
dblane-digicatapult merged commit 0e58557 into main Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants