Vulnerability Scanning¶
OpenMed scans the service container image and dependency lockfile before a change can ship. The gate is intentionally separate from pip-audit: the existing dependency policy still covers fixable Python advisories, while this workflow adds operating-system and container-package CVE coverage.
The GitHub Actions workflow lives at .github/workflows/vuln-scan.yml. It:
- builds the root
Dockerfileasopenmed:ci - scans the image with Trivy for operating-system and library CVEs
- scans
uv.lockwith Trivy's filesystem dependency scanner - enforces the result with
scripts/security/vulnerability_scan_gate.py - uploads JSON summaries and SARIF reports as the
vulnerability-scan-reportsartifact
Severity threshold¶
The default blocking threshold is HIGH, configured in deploy/security/cve-allowlist.yaml:
Manual workflow runs can override the threshold with the severity-threshold input. CI also respects the VULN_SCAN_SEVERITY_THRESHOLD environment variable. Valid severities are UNKNOWN, LOW, MEDIUM, HIGH, and CRITICAL.
Waiver process¶
Waivers are only for triaged vulnerabilities that cannot be fixed immediately. Each entry must be time-boxed and explain why it exists:
allowlist:
- id: CVE-2026-12345
package: openssl
target: "openmed:ci (debian 12)"
expires: 2026-08-15
reason: Waiting for the Debian slim base image to publish a fixed package.
id, expires, and reason are required. package and target are optional match constraints. Use them when the same CVE may appear in more than one layer or dependency source. Expired waivers fail closed and must be removed or renewed with a new review date.
Before adding a waiver:
- Confirm the finding is not fixed by updating
Dockerfile,uv.lock, or the base image. - Record the smallest matching scope possible.
- Set an expiry date that reflects the next patch review, not an open-ended release target.
- Remove the waiver as soon as a fixed package is available.
Local verification¶
Run the gate against existing Trivy JSON reports:
uv run python scripts/security/vulnerability_scan_gate.py \
--report vulnerability-reports/trivy-image.json \
--report vulnerability-reports/trivy-lockfile.json \
--allowlist deploy/security/cve-allowlist.yaml \
--output vulnerability-reports/vulnerability-scan-summary.json
The workflow also includes seeded checks. A synthetic high-severity finding must be rejected, and the same finding must pass only when covered by an active waiver. These checks keep threshold and waiver behavior from silently drifting.