Skip to main content

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

.github/
├── workflows/
│   └── security-image-cve-scan.yml  # Main scanning workflow
├── scripts/
│   └── extract-images.sh            # Image extraction script
└── security/
    └── additional-images.yaml       # Extra images to scan

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:
  1. Kubernetes manifests: Parses image: and imageName: fields from kubernetes/rendered/<environment>/
  2. Additional images file: Manual list in .github/security/additional-images.yaml for images not in manifests (e.g., operator-pulled images, sidecar injectors)
  3. Workflow input: Ad-hoc images passed via workflow dispatch
# .github/security/additional-images.yaml
images:
  # Example entries for images not in standard manifests:
  # - docker.io/library/postgres:15-alpine
  # - gcr.io/istio-release/proxyv2:1.20.0

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:
ToolPurpose
SyftGenerates CycloneDX SBOM from container images
GrypeMatches SBOM packages against CVE databases
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

🔍 CVE Scan Report

Environment: production
Scan Date: YYYY-MM-DD HH:MM:SS UTC

Summary

SeverityCount
🔴 Critical0
🟠 High0
🟡 Medium8
🔵 Low12
⚪ Negligible24
❓ Unknown0
Successful scans: 5
Failed scans: 0

Results by Image

ImageStatusCriticalHighMediumLow
ghcr.io/cloudnative-pg/postgresql:17.20045
<ecr>/go-backend:0.2.10023
<ecr>/go-backend/migrations:0.2.10023
quay.io/argoproj/argocd:v2.13.30001
docker.io/traefik:v3.6.50000

Usage

The workflow runs daily at 5 AM UTC (midnight EST) against production images. It can also be triggered manually via workflow_dispatch to scan a specific environment (production, staging, or none) or ad-hoc images.

Key Design Decisions

DecisionRationale
Daily scheduled scansCatch newly disclosed CVEs in existing images
Extract from rendered manifestsScan exactly what’s deployed, not source templates
Parallel per-image jobsFast scanning with isolated failures
SBOM generationEnables offline analysis and compliance reporting
Fail-fast disabledComplete scan of all images even if some fail

Additional Configurability

Custom Severity Thresholds

To fail the workflow on critical vulnerabilities, modify the report job:
- name: Fail on critical vulnerabilities
  if: steps.report.outputs.total_critical != '0'
  run: |
    echo "::error::Found ${{ steps.report.outputs.total_critical }} critical vulnerabilities"
    exit 1

Slack/Email Notifications

Add a notification step after the report job to alert on scan results.

Vulnerability Suppression

Use Grype’s ignore rules for false positives or accepted risks.