Skip to content

Container Scanning

Automated Docker container vulnerability scanning using Trivy, with findings surfaced in the GitHub Security tab.

Trivy runs via the central reusable reusable-trivy.yml (from zircote/.github), invoked from two callers:

FieldValue
Filesystem scanquality-gates.yml (job trivy) — IaC + license scan over the source tree, at merge time
Image scanpipeline.yml (job gate-image) — Trivy image scan, publish-gated (dormant in template state)
Image attestationpipeline.yml (job attest-container-scan) — binds the image scan verdict to the image digest as a container-scan/v1 attestation
IntegrationGitHub Security tab (SARIF upload)

Trivy scans Docker images for:

  • OS package vulnerabilities (CVEs)
  • Application dependency vulnerabilities
  • Misconfigurations
  • Secrets in image layers

Severity levels: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN.

Two Trivy lanes run through the central reusable workflow:

  1. Filesystem (merge-time)quality-gates.yml scans the source tree (Dockerfile, manifests, licenses) and uploads SARIF to GitHub Security.
  2. Image (publish-gated) — once the container build is armed (publish = false deleted), pipeline.yml’s gate-image job runs a Trivy image scan against the built image digest. The attest-container-scan job then signs the scan result and binds it to the image digest as a container-scan/v1 attestation.

Filesystem findings appear in Security tab → Code scanning alerts. Image scan findings become a signed container-scan/v1 attestation on the image (verifiable with gh attestation verify).

{
"results": [
{
"ruleId": "CVE-2021-12345",
"level": "error",
"message": {
"text": "openssl: buffer overflow vulnerability"
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": "Dockerfile"
}
}
}]
}
]
}
Library Vulnerability Severity Status Installed Fixed
------- ------------- -------- ------ --------- -----
openssl CVE-2021-12345 CRITICAL fixed 1.1.1k 1.1.1l

The filesystem gate in quality-gates.yml re-runs weekly on a schedule, so a previously clean tree is re-checked against newly disclosed CVEs:

schedule:
- cron: "0 6 * * 1" # Every Monday at 06:00 UTC

The image scan in pipeline.yml is event-driven (on push/tag once publishing is armed), not scheduled.

Terminal window
# Install Trivy
brew install trivy
# or
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Build and scan image
docker build -t rust-template:local .
trivy image rust-template:local
# Scan a specific severity
trivy image --severity HIGH,CRITICAL rust-template:local
# Output formats
trivy image --format json rust-template:local > scan.json
trivy image --format sarif rust-template:local > scan.sarif

Verify: trivy image rust-template:local prints a vulnerability table.

Trivy behaviour is configured in the central reusable workflow (zircote/.github), not in this repo. To adjust severity for a local scan:

Terminal window
trivy image --severity CRITICAL,HIGH rust-template:local

Verify: confirm only the selected severities are reported.

For a local scan, drop vulnerabilities that have no available fix:

Terminal window
trivy image --ignore-unfixed rust-template:local

Verify: vulnerabilities with no available fix no longer appear.

Create .trivyignore:

# Ignore specific CVEs
CVE-2021-12345
# Ignore by package
pkg:deb/debian/openssl@1.1.1

Verify: trivy image rust-template:local no longer lists the ignored entries.

  1. Update the base image (pin by digest for immutability):

    # Before
    FROM rust:1.92-slim
    # After (with digest for immutability)
    FROM rust:1.92-slim@sha256:abc123...
  2. Update dependencies and rebuild:

    Terminal window
    cargo update
    cargo audit
    docker build -t rust-template:patched .
    trivy image rust-template:patched
  3. Accept a documented risk (false positive or mitigated):

    .trivyignore
    CVE-2021-12345 # Mitigated by network isolation

Verify: re-scan the rebuilt image and confirm the finding is resolved or suppressed.

Scan failures:

Terminal window
trivy image --download-db-only
trivy image --clear-cache

False positives — inspect the finding, then suppress if confirmed:

Terminal window
trivy image --format json rust-template:local | jq '.Results[].Vulnerabilities[] | select(.VulnerabilityID=="CVE-2021-12345")'

Slow scans:

Terminal window
# Scan only critical/high
trivy image --severity CRITICAL,HIGH rust-template:local
# Skip DB download (use cache)
trivy image --skip-db-update rust-template:local

A vulnerable dependency or base image is a vulnerability in the shipped artifact, even when the application source is clean. Scanning the built image — not just Cargo.lock — catches OS-level CVEs, misconfigurations, and leaked secrets that source-level audits miss. Surfacing the filesystem findings as SARIF in the GitHub Security tab puts them where reviewers already work and gives each one a tracked lifecycle, while the image scan verdict is signed and bound to the image digest as a verifiable attestation. The weekly filesystem schedule re-checks the source against newly disclosed CVEs so a previously clean tree doesn’t silently age into a vulnerable one.