Methodology
How ce.rodeo scores an image, ranks a publisher, dates a build, and decides what to index. The grades page has the per-signal rubric; this page is the governance surface: what we commit to, how we change it, and the dated history of every change.
Image dating
Freshness ("rebuilt in the last 90 days") used to read config.Created from the image
manifest, which reproducible-build publishers set to their source commit date. That penalised reproducibility.
From 2026-10-21, freshness reads first_seen_at_cerodeo — the first time our scanner
observed the image digest. That date is monotonic-forward: nothing a publisher does can push it later. For images
that pre-date the column, we back-filled with coalesce(last_pushed_at, created_at).
Severity source
Grype's native severity varies by data source — Ubuntu Security Tracker for Ubuntu packages, NVD for much of Debian's unfixed tail, GHSA for language ecosystems. That drift was a defensible choice at launch; it isn't anymore. For CVE severity, ce.rodeo now rebuckets every match by its CVSS v3 base score — Critical ≥ 9.0, High ≥ 7.0, Medium ≥ 4.0, Low < 4.0 — using NVD's own thresholds. When a match has no CVSS v3 record, we fall back to grype's per-source rating; treating "no NVD rating" as a filter would hide real exposure. The downloadable grype JSON on every image page carries the full raw matches so you can rebucket differently.
Publisher rank: v2 (current) and v3 (preview)
Rank v2 grades each (publisher, project) product by the best 48h-grace scoring_grade
across every variant in the 60-day push window. Rank = (3·A + B − 0.5·F) ÷ products_60d
× ln(1 + products_60d). Shipped 2026-10-19.
Rank v3 (preview, switches 2026-11-04) grades each product by the newest-pushed variant in the 60-day window — one image, no best-of-window hiding. Ordering key is per-product quality with a Wilson-style small-sample correction (lower bound of a 95% confidence interval), so a publisher with 1 clean product can't tie one with 50. Breadth becomes a separate column: "products_60d", not a multiplier.
Both rankings are live in the API during the dual-run. Pass ?variant=v3 to
/v1/publishers or open /purveyors?variant=v3
to preview the v3 leaderboard.
Scan cadence
The hourly rematch covers freshest images first with namespace round-robin inside each week bucket. Before 2026-10-21, older images could go 6–11 days without a rescan while fresh CVE matches queued up in the DB. From that date, every tracked image is rematched against each Grype DB pull (every 4h) via a companion systemd service. The SBOM/content dedupe keeps the extra load cheap — most hourly cycles rematch rather than rescan.
Measurement bugs fixed on 2026-10-21
- HEALTHCHECK bonus (+3). Was earned by 0 of 775 images because the collector read the
normalized OCI image config (which drops Docker's Healthcheck field). Now reads the raw config blob from the
manifest's
config.digest. - BuildKit in-index attestations. Only cosign-sidecar attestations were credited; the in-index
format Docker Official ships with was ignored. The scanner now walks the image index for
vnd.docker.reference.digest-annotated sibling manifests and credits SLSA / SPDX / subject-digest from either source. - Cosign signature now verified, not presence-credited. Previously the bonus fired whenever
cosign triangulatereturned a signature tag that resolved. Now it fires only whencosign verifysucceeds against the publisher's declared identity inpublisher_signing_identities. Publishers whose identity we haven't captured get no bonus — strictly more honest than the prior "signature-tag-exists" credit. - Upstream-release check (+3) with tag suffixes. Compared the whole tag string, so "15.7.0-CE" against upstream "v15.7.0" failed. Now compares the parsed semver core.
- Image date single-source. Two code paths wrote the image's "pushed" date with mismatched
semantics. The rubric now reads
first_seen_at_cerodeo; manifest Created is retained for enumeration ordering only.
Changelog
From 2026-10-21 onward, every ranking or rubric change is announced here before it ships, dual-runs for ~2 weeks, and links to its effect on the top 10 publishers.
- 2026-11-04 rank-v3 cerodeo-v1
Publisher rank v3: newest release wins, quality × Wilson small-sample correction
Dual-run with v2 from 2026-10-21 through 2026-11-04. v3 grades each (publisher, project) by the single newest-pushed image's 48h-grace scoring_grade (not the best of any variant in the window). Primary ordering key is per_product_quality with a Wilson lower-bound small-sample correction; products_60d (breadth) becomes a separate column rather than a multiplier.
- 2026-10-21 rank-v2.1 cerodeo-v1DUAL-RUN until 2026-11-04
Governance overhaul: unified ranks, operator-unranked, methodology dual-run
Three disagreeing rank fields (leaderboard v2, publisher dossier v1, API /v1/publishers/:name alternate) reduced to one source of truth. ReadyStack listed unranked, scored like every other publisher, left out of the leaderboard ordering. Hard-coded project and publisher lists replaced with a public policy table. Four measurement bugs fixed in the scanner: unreachable HEALTHCHECK bonus, missed BuildKit in-index attestations, cosign presence-not-verified, upstream-release suffix compare. Severity normalized to CVSS v3 base score where available, falling back to grype's per-source rating. Freshness dated from first-sighting on ce.rodeo, not config.Created. Scanner now rescans every tracked image on each Grype DB update (not just the hourly subset).
- 2026-10-19 rank-v2 cerodeo-v1
Publisher rank v2: product-level, 60-day scope, log-damped breadth
Replaced the all-time per-image trust_score with per_product_quality × ln(1+products_60d) over products pushed in the last 60 days. Change announced the same day the switch happened; this did not follow the dual-run principle introduced on 2026-10-21.
- 2026-10-06 rank-v1 cerodeo-v1
Initial rubric and publisher rank
First published rubric (cerodeo-v1) and the all-time, per-image publisher trust_score. Shipped alongside the first scored snapshot in early October.
Think a principle should be stronger? Open an issue: github.com/c0inz/cerodeo/issues.