Chainguard Comparison

Collects CVE posture data comparing Hummingbird and Chainguard container images and surfaces the results in the Hummingbird dashboard under the Competitive tab.

Features

  • Fetches Hummingbird CVE data from the catalog API — the same source used by images.redhat.com, updated hourly by the scan pipeline
  • Scans Chainguard images with Grype (the only available source for Chainguard CVE data)
  • Stores results in PostgreSQL for trend tracking and historical comparison
  • Dashboard shows per-image CVE counts, severity breakdowns (C/H/M/L), delta (HB minus CG), 30-day trend charts, and aggregate summary stats
  • Runs every 6 hours as a Kubernetes CronJob

Prerequisites

  • Access to the Hummingbird cluster (PostgreSQL, image pull credentials)
  • The chainguard_comparison package from competitive-tools installed in the container image

Deployment

The tool is deployed as a Kubernetes CronJob. The container image is built by Konflux from chainguard-comparison/Containerfile.

Environment variables

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string, e.g. postgresql://user:pass@host/db
GRYPE_TIMEOUT No Per-image Grype scan timeout in seconds (default: 300)
SENTRY_DSN No Sentry DSN for error reporting

How it works

  1. Discovery — lists Hummingbird images from the catalog API and Chainguard images via the OCI distribution API
  2. Matching — pairs images by name using the alias map in competitive-tools (e.g. nodejs ↔ node, postgresql ↔ postgres)
  3. HB collection — calls GET /v1/images/{name}/vulnerabilities/{tag} on the catalog API; no image pull or local scan required
  4. CG collection — pulls each Chainguard image and runs grype locally; Chainguard has no publicly accessible pre-scanned CVE feed
  5. Storage — writes both sides to the chainguard_scans table in PostgreSQL

Dashboard

The /competitive page in hummingbird-dashboard shows:

  • Summary cards: total images compared, count worse/better/same
  • Sortable table with per-image CVE counts and delta
  • Inline severity breakdown: Critical / High / Medium / Low
  • Per-image 30-day trend chart (click trend on any row)
  • Filter box for quick image lookup

Database schema

CREATE TABLE chainguard_scans (
    id          SERIAL PRIMARY KEY,
    collected_at TIMESTAMPTZ NOT NULL,
    source      TEXT NOT NULL,        -- 'hummingbird' or 'chainguard'
    image_name  TEXT NOT NULL,
    tag         TEXT NOT NULL DEFAULT 'latest',
    image_ref   TEXT,
    total_cves  INTEGER NOT NULL DEFAULT 0,
    critical    INTEGER NOT NULL DEFAULT 0,
    high        INTEGER NOT NULL DEFAULT 0,
    medium      INTEGER NOT NULL DEFAULT 0,
    low         INTEGER NOT NULL DEFAULT 0,
    negligible  INTEGER NOT NULL DEFAULT 0,
    unknown     INTEGER NOT NULL DEFAULT 0
);

The table is created by the Alembic migration b4e7f8a1c3d5_add_chainguard_scans.py.

Development

cd chainguard-comparison
pip install -e ".[dev]"
python -m pytest tests/

To run the collector locally against a development database:

export DATABASE_URL=postgresql://postgres:dev@localhost:5432/events
python -m cg_comparison

License

GPL-3.0-or-later