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
1
Extract images
Collects container images from rendered Kubernetes manifests for the target environment, plus any additional images from configuration.
2
Generate SBOM
Creates a Software Bill of Materials (SBOM) for each image using Syft.
3
Scan for vulnerabilities
Analyzes each SBOM with Grype to identify known CVEs.
4
Aggregate report
Consolidates results into a summary report with vulnerability counts by severity.
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: