# Chainguard Comparison

LLMS index: [llms.txt](https://hummingbird-project.io/llms.txt) | Full content: [llms-full.txt](https://hummingbird-project.io/llms-full.txt)

---

Collects CVE posture data comparing Hummingbird and Chainguard container images
and surfaces the results in the [Hummingbird dashboard][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](https://github.com/anchore/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](https://gitlab.com/org/group/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

```sql
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

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

To run the collector locally against a development database:

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

## License

GPL-3.0-or-later

[dashboard]: https://hummingbird-project.io/docs/background/tools/hummingbird-dashboard/
