CVE Scanning¶
Container image CVE scanning is integrated into every custom image CI pipeline using Syft (SBOM generation) and Grype (vulnerability matching).
Pipeline Integration¶
flowchart LR
code["Source / upstream bump"] --> build["build\ndocker buildx\nmulti-arch"] --> scan["scan\nSyft → SBOM\nGrype → CVE match"] --> notify["notify\nPushover priority 1\n(HIGH/CRITICAL only)"]
build --> push["push :latest\nregistry.mdapi.ch/mdapi/"]
scan --> artifact["SBOM artifact\nSPDX JSON\n7-day retention"]
The scan stage runs after the image is built but the result never blocks the pipeline (allow_failure: true). A CVE finding sends a notification for human review — the deployment still proceeds because production systems cannot be held hostage by upstream vulnerabilities that may have no fix yet.
Covered Images¶
9 custom images are scanned on every CI run (triggered on push, and every 4 hours via schedule):
| Image | Base | Registry |
|---|---|---|
| gitlab-webservice-ee | GitLab EE | registry.mdapi.ch/mdapi/ |
| gitlab-sidekiq-ee | GitLab EE | registry.mdapi.ch/mdapi/ |
| keycloak | quay.io/keycloak/keycloak:26.x | registry.mdapi.ch/mdapi/ |
| chrony | debian:stable-slim | registry.mdapi.ch/mdapi/ |
| certspotter | golang | registry.mdapi.ch/mdapi/ |
| autoconfig | nginx:alpine | registry.mdapi.ch/mdapi/ |
| opennic-tier2 | debian:stable-slim | registry.mdapi.ch/mdapi/ |
| threadfin | debian:stable-slim | registry.mdapi.ch/mdapi/ |
| joplin-mcp | python:slim | registry.mdapi.ch/mdapi/ |
| znc | debian:stable-slim | registry.mdapi.ch/mdapi/ |
SBOM Artifacts¶
Syft generates an SPDX JSON SBOM for each image build. This provides:
- A point-in-time snapshot of every package installed in the image
- A queryable artifact for retroactive CVE analysis when new vulnerabilities are published
- Compliance evidence for software supply chain auditing
SBOM artifacts are stored in GitLab CI for 7 days per build.
Runtime CVE Scanning¶
A Windmill flow (f/security/pod_image_cve_scan) runs daily and scans the images of all currently running pods against the Grype database. This catches:
- Third-party images (from docker.io, ghcr.io) that are not built from source
- Newly published CVEs affecting images that passed scan when built
Results are sent via Pushover with the affected image and CVE IDs.