Overview
The image CVE scanning workflow automatically scans container images for known vulnerabilities:- Scheduled scans: Runs daily against production images to catch newly disclosed CVEs
- On-demand scanning: Can be triggered manually for any environment or specific images
- Comprehensive coverage: Extracts images from rendered Kubernetes manifests plus manually-specified images
- Detailed reporting: Generates SBOM and vulnerability reports with severity breakdowns
What’s Included
Workflow Structure
Scanning Process
Extract images
Collects container images from rendered Kubernetes manifests for the target environment, plus any additional images from configuration.
Generate SBOM
Creates a Software Bill of Materials (SBOM) for each image using Syft.
Scan for vulnerabilities
Analyzes each SBOM with Grype to identify known CVEs.
Image Discovery
Images are extracted from multiple sources:- Kubernetes manifests: Parses
image:andimageName:fields fromkubernetes/rendered/<environment>/ - Additional images file: Manual list in
.github/security/additional-images.yamlfor images not in manifests (e.g., operator-pulled images, sidecar injectors) - Workflow input: Ad-hoc images passed via workflow dispatch
Vulnerability Scanning
Each image is scanned in a separate parallel job using GitHub Actions matrix strategy, enabling fast scans even with many images. The workflow uses the Anchore toolchain:
The workflow handles both public images and private ECR images (via OIDC authentication).
Report Output
The workflow generates a GitHub Actions job summary with:- Severity breakdown: Critical, High, Medium, Low, Negligible, Unknown counts
- Per-image results: Table showing vulnerability counts for each scanned image
- Scan artifacts: Full Grype JSON output and SBOMs retained for 30 days
Example scan report
Example scan report
🔍 CVE Scan Report
Environment: productionScan Date: YYYY-MM-DD HH:MM:SS UTC
Summary
| Severity | Count |
|---|---|
| 🔴 Critical | 0 |
| 🟠 High | 0 |
| 🟡 Medium | 8 |
| 🔵 Low | 12 |
| ⚪ Negligible | 24 |
| ❓ Unknown | 0 |
Failed scans: 0
Results by Image
| Image | Status | Critical | High | Medium | Low |
|---|---|---|---|---|---|
ghcr.io/cloudnative-pg/postgresql:17.2 | ✅ | 0 | 0 | 4 | 5 |
<ecr>/go-backend:0.2.1 | ✅ | 0 | 0 | 2 | 3 |
<ecr>/go-backend/migrations:0.2.1 | ✅ | 0 | 0 | 2 | 3 |
quay.io/argoproj/argocd:v2.13.3 | ✅ | 0 | 0 | 0 | 1 |
docker.io/traefik:v3.6.5 | ✅ | 0 | 0 | 0 | 0 |
Usage
The workflow runs daily at 5 AM UTC (midnight EST) against production images. It can also be triggered manually viaworkflow_dispatch to scan a specific environment (production, staging, or none) or ad-hoc images.
Key Design Decisions
| Decision | Rationale |
|---|---|
| Daily scheduled scans | Catch newly disclosed CVEs in existing images |
| Extract from rendered manifests | Scan exactly what’s deployed, not source templates |
| Parallel per-image jobs | Fast scanning with isolated failures |
| SBOM generation | Enables offline analysis and compliance reporting |
| Fail-fast disabled | Complete scan of all images even if some fail |
Additional Configurability
Custom Severity Thresholds
To fail the workflow on critical vulnerabilities, modify thereport job: