Summary
Add two related CLI commands for proactive dependency/vulnerability visibility, complementing the existing cds list images (Docker Hub tag-freshness check, cli/image_updates.py) and cds security --verify-images (signature/provenance verification, cli/image_verification.py):
cds list packages (or cds check-updates) — check Python package floors declared in images/*/requirements.txt against the latest available versions on PyPI, so outdated pins are visible without waiting for a Trivy CVE hit.
cds security --scan-images (or a new cds scan subcommand) — run Trivy vulnerability scanning against the actual images a profile resolves to, not just the fixed set under images/.
Motivation
While investigating issue #174-adjacent work, a local Trivy scan of cds/superset flagged 20 HIGH linux-libc-dev CVEs and a HIGH cryptography CVE. Rebuilding with docker build --no-cache --pull (fresh apt-get upgrade + uv pip install --upgrade) resolved all of them — the vulnerable image was a stale local Docker layer cache artifact, not a real gap in main. This highlighted two gaps:
- There's no CLI-level way to check whether
images/*/requirements.txt floors (cryptography>=48.0.1, etc., see tests/test_superset_hardening.py::test_vulnerable_python_packages_are_patched) are still current against upstream PyPI releases — today that only happens reactively, via a Trivy CVE finding or a manually-noticed rebuild.
cds security --verify-images (cli/image_verification.py) already resolves a profile's rendered Compose and checks image references for policy (registry allowlist, digest pinning, signatures/provenance), but does not run an actual vulnerability scan (Trivy or otherwise) against those images. A profile can reference module images beyond the two under images/ (any Docker Hub image a module's compose.services.*.image points to), and there's currently no single cds command that scans exactly what a given profile would deploy.
Proposed scope
1. Outdated-package check
- New command (name TBD —
cds list packages fits the existing cds list {profiles,modules,images} family) that:
- discovers
images/*/requirements.txt files (mirroring how cli/image_updates.py::collect_module_images discovers module.yaml files),
- queries PyPI's JSON API (
https://pypi.org/pypi/<package>/json) for the latest release per pinned/floor package,
- reports package name, declared floor, latest available version, and whether the floor is stale.
- Should reuse the existing network-call conventions in
cli/image_updates.py (timeout, retry/backoff, CDS_* env var for tuning, offline-friendly failure mode) rather than introducing a second HTTP client pattern.
- Out of scope: resolving actual installed versions inside a built image (see the testing question below — that's a build-time verification concern, not a "what's out there" check).
2. Per-profile Trivy scan
- Extend
cds security (as a new flag, e.g. --scan-images, alongside the existing --verify-images) or add a new subcommand that:
- resolves the profile the same way
_run_image_verification does (resolve_profile → build_plan → render_compose),
- collects the resulting
services.*.image references,
- shells out to
trivy image (or the aquasecurity/trivy-action-equivalent local invocation) per image, with the same --severity HIGH,CRITICAL --ignore-unfixed policy used in .github/workflows/image-security-scan.yml,
- fails closed (non-zero exit) on findings, matching
--verify-images' fail-closed behavior on plan/render failure.
- Needs a documented behavior for images that aren't buildable locally (e.g. pull digest-pinned images instead of building, similar to the scheduled-scan path in
image-security-scan.yml that scans published digests from tests/fixtures/signed-images.json instead of rebuilding).
- Should degrade gracefully (clear error, not a crash) when the
trivy binary isn't on PATH, matching how cli/main.py's up command reports "docker was not found" rather than crashing.
Related: dynamic requirements-floor verification test
Separately raised: should there be a test asserting images/*/requirements.txt floors match what's actually installed in a built image (via pip freeze/uv pip list inside the container), rather than only the static text-based check in tests/test_superset_hardening.py::test_vulnerable_python_packages_are_patched?
Recommendation: yes, but as a slower, separately-gated test tier, not part of the default python -m unittest discover loop:
- The static test is cheap and catches "forgot to bump the floor in requirements.txt" regressions — keep it as-is in the fast suite.
- A dynamic test needs Docker and a real (ideally
--no-cache) build, which is exactly the kind of test this repo already isolates elsewhere (see tests/test_compose_runtime_smoke.py's shutil.which("docker")/docker info skip guard). It should live alongside those Docker-dependent tests, skip cleanly when Docker is unavailable, and run in CI on a schedule and/or on images/**/requirements.txt changes — mirroring .github/workflows/image-security-scan.yml's existing pull_request: paths: ["images/**", ...] trigger, since that's already the workflow rebuilding these images fresh on every relevant change.
- This would have caught our local stale-cache false positive faster (or rather, proven that
main never had a problem) without needing an ad hoc manual rebuild-and-diff.
Acceptance criteria
cds list packages (naming TBD) reports outdated requirements.txt floors across all images/*/requirements.txt files without requiring Docker.
cds security --scan-images (naming TBD) runs Trivy against a resolved profile's images and fails closed on HIGH/CRITICAL findings, consistent with --verify-images's existing fail-closed conventions.
- A new (or extended) Docker-gated test verifies
images/*/requirements.txt floors against actual installed versions in a freshly-built image, skipping cleanly without Docker.
- Both commands documented in the relevant
docs/ file(s) (README command reference and/or a new docs/image-scanning.md-style doc if one exists by the time this is implemented).
Out of scope
- Replacing Trivy with a different scanner.
- Automatically bumping
requirements.txt floors (that's dependency-update tooling, e.g. Renovate, which already runs in this repo).
Summary
Add two related CLI commands for proactive dependency/vulnerability visibility, complementing the existing
cds list images(Docker Hub tag-freshness check,cli/image_updates.py) andcds security --verify-images(signature/provenance verification,cli/image_verification.py):cds list packages(orcds check-updates) — check Python package floors declared inimages/*/requirements.txtagainst the latest available versions on PyPI, so outdated pins are visible without waiting for a Trivy CVE hit.cds security --scan-images(or a newcds scansubcommand) — run Trivy vulnerability scanning against the actual images a profile resolves to, not just the fixed set underimages/.Motivation
While investigating issue #174-adjacent work, a local Trivy scan of
cds/supersetflagged 20 HIGHlinux-libc-devCVEs and a HIGHcryptographyCVE. Rebuilding withdocker build --no-cache --pull(freshapt-get upgrade+uv pip install --upgrade) resolved all of them — the vulnerable image was a stale local Docker layer cache artifact, not a real gap inmain. This highlighted two gaps:images/*/requirements.txtfloors (cryptography>=48.0.1, etc., seetests/test_superset_hardening.py::test_vulnerable_python_packages_are_patched) are still current against upstream PyPI releases — today that only happens reactively, via a Trivy CVE finding or a manually-noticed rebuild.cds security --verify-images(cli/image_verification.py) already resolves a profile's rendered Compose and checks image references for policy (registry allowlist, digest pinning, signatures/provenance), but does not run an actual vulnerability scan (Trivy or otherwise) against those images. A profile can reference module images beyond the two underimages/(any Docker Hub image a module'scompose.services.*.imagepoints to), and there's currently no singlecdscommand that scans exactly what a given profile would deploy.Proposed scope
1. Outdated-package check
cds list packagesfits the existingcds list {profiles,modules,images}family) that:images/*/requirements.txtfiles (mirroring howcli/image_updates.py::collect_module_imagesdiscoversmodule.yamlfiles),https://pypi.org/pypi/<package>/json) for the latest release per pinned/floor package,cli/image_updates.py(timeout, retry/backoff,CDS_*env var for tuning, offline-friendly failure mode) rather than introducing a second HTTP client pattern.2. Per-profile Trivy scan
cds security(as a new flag, e.g.--scan-images, alongside the existing--verify-images) or add a new subcommand that:_run_image_verificationdoes (resolve_profile→build_plan→render_compose),services.*.imagereferences,trivy image(or theaquasecurity/trivy-action-equivalent local invocation) per image, with the same--severity HIGH,CRITICAL --ignore-unfixedpolicy used in.github/workflows/image-security-scan.yml,--verify-images' fail-closed behavior on plan/render failure.image-security-scan.ymlthat scans published digests fromtests/fixtures/signed-images.jsoninstead of rebuilding).trivybinary isn't onPATH, matching howcli/main.py'supcommand reports "docker was not found" rather than crashing.Related: dynamic requirements-floor verification test
Separately raised: should there be a test asserting
images/*/requirements.txtfloors match what's actually installed in a built image (viapip freeze/uv pip listinside the container), rather than only the static text-based check intests/test_superset_hardening.py::test_vulnerable_python_packages_are_patched?Recommendation: yes, but as a slower, separately-gated test tier, not part of the default
python -m unittest discoverloop:--no-cache) build, which is exactly the kind of test this repo already isolates elsewhere (seetests/test_compose_runtime_smoke.py'sshutil.which("docker")/docker infoskip guard). It should live alongside those Docker-dependent tests, skip cleanly when Docker is unavailable, and run in CI on a schedule and/or onimages/**/requirements.txtchanges — mirroring.github/workflows/image-security-scan.yml's existingpull_request: paths: ["images/**", ...]trigger, since that's already the workflow rebuilding these images fresh on every relevant change.mainnever had a problem) without needing an ad hoc manual rebuild-and-diff.Acceptance criteria
cds list packages(naming TBD) reports outdatedrequirements.txtfloors across allimages/*/requirements.txtfiles without requiring Docker.cds security --scan-images(naming TBD) runs Trivy against a resolved profile's images and fails closed on HIGH/CRITICAL findings, consistent with--verify-images's existing fail-closed conventions.images/*/requirements.txtfloors against actual installed versions in a freshly-built image, skipping cleanly without Docker.docs/file(s) (README command reference and/or a newdocs/image-scanning.md-style doc if one exists by the time this is implemented).Out of scope
requirements.txtfloors (that's dependency-update tooling, e.g. Renovate, which already runs in this repo).