Container Scanning
Automated Docker container vulnerability scanning using Trivy, with findings surfaced in the GitHub Security tab.
Reference
Section titled “Reference”Trivy runs via the central reusable reusable-trivy.yml (from zircote/.github), invoked from two callers:
| Field | Value |
|---|---|
| Filesystem scan | quality-gates.yml (job trivy) — IaC + license scan over the source tree, at merge time |
| Image scan | pipeline.yml (job gate-image) — Trivy image scan, publish-gated (dormant in template state) |
| Image attestation | pipeline.yml (job attest-container-scan) — binds the image scan verdict to the image digest as a container-scan/v1 attestation |
| Integration | GitHub Security tab (SARIF upload) |
What it scans for
Section titled “What it scans for”Trivy scans Docker images for:
- OS package vulnerabilities (CVEs)
- Application dependency vulnerabilities
- Misconfigurations
- Secrets in image layers
Severity levels: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN.
CI pipeline stages
Section titled “CI pipeline stages”Two Trivy lanes run through the central reusable workflow:
- Filesystem (merge-time) —
quality-gates.ymlscans the source tree (Dockerfile, manifests, licenses) and uploads SARIF to GitHub Security. - Image (publish-gated) — once the container build is armed (
publish = falsedeleted),pipeline.yml’sgate-imagejob runs a Trivy image scan against the built image digest. Theattest-container-scanjob then signs the scan result and binds it to the image digest as acontainer-scan/v1attestation.
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).
SARIF output (GitHub Security)
Section titled “SARIF output (GitHub Security)”{ "results": [ { "ruleId": "CVE-2021-12345", "level": "error", "message": { "text": "openssl: buffer overflow vulnerability" }, "locations": [{ "physicalLocation": { "artifactLocation": { "uri": "Dockerfile" } } }] } ]}Table output
Section titled “Table output”Library Vulnerability Severity Status Installed Fixed------- ------------- -------- ------ --------- -----openssl CVE-2021-12345 CRITICAL fixed 1.1.1k 1.1.1lScheduled scans
Section titled “Scheduled scans”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 UTCThe image scan in pipeline.yml is event-driven (on push/tag once publishing is armed), not scheduled.
How-to
Section titled “How-to”Scan locally
Section titled “Scan locally”# Install Trivybrew install trivy# orcurl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Build and scan imagedocker build -t rust-template:local .trivy image rust-template:local
# Scan a specific severitytrivy image --severity HIGH,CRITICAL rust-template:local
# Output formatstrivy image --format json rust-template:local > scan.jsontrivy image --format sarif rust-template:local > scan.sarifVerify: trivy image rust-template:local prints a vulnerability table.
Configure the severity threshold
Section titled “Configure the severity threshold”Trivy behaviour is configured in the central reusable workflow (zircote/.github), not in this repo. To adjust severity for a local scan:
trivy image --severity CRITICAL,HIGH rust-template:localVerify: confirm only the selected severities are reported.
Ignore unfixed vulnerabilities
Section titled “Ignore unfixed vulnerabilities”For a local scan, drop vulnerabilities that have no available fix:
trivy image --ignore-unfixed rust-template:localVerify: vulnerabilities with no available fix no longer appear.
Suppress specific findings
Section titled “Suppress specific findings”Create .trivyignore:
# Ignore specific CVEsCVE-2021-12345
# Ignore by packagepkg:deb/debian/openssl@1.1.1Verify: trivy image rust-template:local no longer lists the ignored entries.
Remediate a finding
Section titled “Remediate a finding”-
Update the base image (pin by digest for immutability):
# BeforeFROM rust:1.92-slim# After (with digest for immutability)FROM rust:1.92-slim@sha256:abc123... -
Update dependencies and rebuild:
Terminal window cargo updatecargo auditdocker build -t rust-template:patched .trivy image rust-template:patched -
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.
Troubleshooting
Section titled “Troubleshooting”Scan failures:
trivy image --download-db-onlytrivy image --clear-cacheFalse positives — inspect the finding, then suppress if confirmed:
trivy image --format json rust-template:local | jq '.Results[].Vulnerabilities[] | select(.VulnerabilityID=="CVE-2021-12345")'Slow scans:
# Scan only critical/hightrivy image --severity CRITICAL,HIGH rust-template:local
# Skip DB download (use cache)trivy image --skip-db-update rust-template:localWhy this matters
Section titled “Why this matters”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.