Skip to content

Add cds commands for outdated-package checks and per-profile Trivy scanning #386

Description

@RonaldHensbergen

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):

  1. 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.
  2. 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_profilebuild_planrender_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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cliCLI commands and behaviorarea:securitySecurity policies and checksenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions