Guidance for scanning, triaging, and mitigating container image vulnerabilities in the viral-ngs Docker image hierarchy.
Container images are scanned for vulnerabilities using Trivy:
- On every PR/push:
docker.ymlscans each image flavor after build (SARIF -> GitHub Security tab, JSON -> artifact) - Weekly schedule:
container-scan.ymlscans the latest published images - Scans filter to CRITICAL/HIGH severity, ignore-unfixed, and apply a Rego policy (
.trivy-ignore-policy.rego) - Per-CVE exceptions go in
.trivyignorewith mandatory justification comments
The Rego policy filters CVEs that are architecturally inapplicable to ephemeral batch containers:
- AV:P (Physical access required) -- containers are cloud-hosted
- AV:A (Adjacent network required) -- no attacker on same network segment
- AV:L + UI:R (Local + user interaction) -- no interactive sessions
- AV:L + PR:H (Local + high privileges) -- containers run non-root
- AV:L + S:U (Local + scope unchanged) -- attacker already has code execution and impact stays within the ephemeral container
Changes to this policy should be reviewed carefully. The comments in the file explain the rationale and risk for each rule.
Python transitive deps: Pin minimum versions in docker/requirements/*.txt. Prefer conda packages over pip. Check conda-forge availability before assuming a version exists -- conda-forge often lags PyPI by days/weeks.
Java fat JARs (picard, gatk, snpeff, fgbio): Bioinformatics Java tools are distributed as uber JARs with all dependencies bundled inside. Trivy detects vulnerable libraries (log4j, commons-compress, etc.) baked into these JARs. Version bumps can cause ARM64 conda solver conflicts because Java tools pull in openjdk -> harfbuzz -> icu version chains that clash with other packages (r-base, boost-cpp, pyicu). Always check:
- Whether the tool is actually flagged by Trivy (don't bump versions unnecessarily)
- Whether the CVE applies (e.g., log4j 1.x is NOT vulnerable to Log4Shell)
- Whether the desired version resolves on ARM64 before pushing
Go binaries: Some conda packages bundle compiled Go binaries (e.g., mafft's dash_client, google-cloud-sdk's gcloud-crc32c). If the binary is unused, delete it in the Dockerfile. Delete from both the installed location and /opt/conda/pkgs/*/ (conda package cache) -- Trivy scans the full filesystem.
Vendored copies: Packages like google-cloud-sdk and setuptools bundle their own copies of Python libraries that may be older than what's in the conda environment. Trivy flags these vendored copies separately. Options: delete the vendored directory (if not needed at runtime), or accept the risk in .trivyignore with justification.
The conda solver on ARM64 (linux-aarch64) is more constrained than amd64 because fewer package builds exist. Common conflict patterns:
- icu version conflicts: Many packages (openjdk, r-base, boost-cpp, pyicu) pin specific icu version ranges. Bumping one package can make the entire environment unsolvable.
- libdeflate/htslib conflicts: lofreq 2.1.5 pins old htslib/libdeflate versions that conflict with newer pillow/libtiff.
- openjdk version escalation: snpeff 5.2+ requires openjdk>=11, 5.3+ requires openjdk>=21. Higher openjdk versions pull in harfbuzz->icu chains that conflict with everything.
When a solver conflict occurs: revert the change, check what version the solver was picking before, and pin to that exact version if it already addresses the CVE.
When triaging a CVE:
- Check the CVSS vector -- does the Rego policy already filter it?
- Identify the source package -- use Trivy JSON output (
PkgName,PkgPath,InstalledVersion) - Check if a fix version exists on conda-forge/bioconda -- not just on PyPI
- Test on ARM64 -- solver conflicts are the most common failure mode
- If the fix version conflicts: consider whether the CVE is exploitable in your deployment model. Document the risk assessment in
.trivyignoreorvulnerability-mitigation-status.md. - If the vulnerable code is unused: delete the binary/file inline in the Dockerfile (same RUN layer as install to avoid bloating images)
| File | Purpose |
|---|---|
.trivy-ignore-policy.rego |
Rego policy for class-level CVE filtering |
.trivyignore |
Per-CVE exceptions with justifications |
.github/workflows/docker.yml |
Build-time scanning (SARIF + JSON) |
.github/workflows/container-scan.yml |
Weekly scheduled scanning |
vulnerability-mitigation-status.md |
Local-only tracking doc (not committed) |