For purveyors
If you ship a container image under your namespace on Docker Hub, GHCR, cgr.dev, or quay.io and ce.rodeo gave you a grade you don't like, this page tells you exactly why and what you can change. Every signal is an input your build pipeline can produce without us.
Why your image got the grade it got
Every image starts at 100. We subtract for open CVEs we discover against the latest Grype vulnerability database. We add back for hygiene signals your image either has or doesn't. The final score maps to a band: A ≥ 85 B ≥ 70 C ≥ 55 D ≥ 40 F < 40.
How to earn each hygiene bonus
These are the eleven signals that make up the plus side of the formula. Each one is binary (you have it, you don't) or thresholded (you pass a minimum). None require you to pay us or tell us anything — we read them straight off the manifest, the attestations, and the registry.
config.User field is set to something other than root, 0, or empty. Add USER appuser (and a matching RUN adduser) near the end of your Dockerfile. If you need root at runtime, you've already told your users there's a risk — document it, don't earn the bonus. Created timestamp (as returned by the registry) is within 90 days of our scan. Set up a scheduled CI job that rebuilds the image weekly or bi-weekly, even if your application code hasn't changed. New base-image CVE fixes land this way. sha256-<digest>.sig tag exists at the same repository, resolvable via cosign triangulate. Sign at publish time: cosign sign --yes <ref>@<digest>. Keyless with Sigstore / Fulcio is free and takes no key management. cosign download attestation --predicate-type=slsa.dev/provenance/v1 returns a DSSE envelope. Use the slsa-github-generator or your CI's equivalent to emit a provenance attestation and cosign attest it. subject[].digest.sha256 equals the actual image manifest digest. Catches detached provenance that doesn't actually match the thing you shipped. If you already have SLSA provenance, this one is usually free — just make sure your attest step runs against the final digest, not an intermediate. cosign download attestation --predicate-type=spdx.dev/Document returns a DSSE envelope. Generate at build time with syft: syft <ref> -o spdx-json=sbom.json and attest it: cosign attest --predicate sbom.json --type spdx <ref>. We don't trust SBOMs we derive ourselves as highly as the ones you sign. Healthcheck stanza. Add a HEALTHCHECK CMD curl -f http://localhost:$PORT/health || exit 1 (or whatever your service's liveness check actually is). Even a simple one earns the signal. title, description, source, version, revision. Add LABEL org.opencontainers.image.title, .description, .source, .version, .revision. Most CI systems can inject version and revision from the git ref automatically. docker buildx build --platform linux/amd64,linux/arm64 --push. Even just adding arm64 earns the +2 and dramatically broadens who can use your image. v1.2.3). Avoid custom version schemes that drift from the upstream — operators lose the trail to the source code. docker run, docker pull, docker-compose, podman run, or a fenced code block mentioning your image ref. Add a 5-line "Quick start" block near the top of your README with a copy-pastable docker run. We parse the Docker Hub full_description field directly. How to lose points
The minus side of the formula is all CVEs, matched by Grype against the SBOM we extract from your image. Grype pulls from GHSA, NVD, Red Hat Security Data, Debian Security Tracker, Ubuntu CVE Tracker, Alpine secdb, PyPA advisory DB, and the npm advisory DB.
- Fixable Critical:
−25each. A fix exists; you haven't rebuilt against it yet. - Fixable High:
−10each. - Fixable Medium:
−3each. - Unfixable Critical:
−8each. No upstream fix yet; still your users' risk to carry, but we discount because you can't act on it today. - Unfixable High:
−3each.
Low- and Negligible-severity findings don't move the score. We count them, we just don't penalize.
The single biggest grade improvement most publishers can make
Rebuild your image on a schedule. You get the +10 "rebuilt in 90 days"
bonus, and because Grype's CVE DB updates every ~4 hours, a fresh rebuild typically also drops fixable CVE counts
against the same source. We regularly see images jump from F to C or C to A on a single scheduled rebuild that
pulled in upstream base-image patches.
How the Purveyors leaderboard works
/purveyors ranks publisher namespaces by per-image quality, not volume:
rank_score = (3 × A_grades + B_grades − 0.5 × F_grades) ÷ scanned_images
So you can't climb the leaderboard by shipping more images that are mostly F-grade — the F count counts against
you. The way up is simple: ship fewer, better-maintained images, or raise the average by patching the F ones.
Publishers with fewer than 3 scanned images get a LOW COVERAGE tag and are held out of
the ranked positions until we have enough signal.
Why your image might not show up at all
ce.rodeo indexes Community Edition images only. We don't track:
- Images whose current version is published under BSL, SSPL, Confluent Community License, or Elastic License v2. For projects that recently flipped (Redis → BSL/SSPL, Terraform → BSL, MongoDB → SSPL) we track the OSI-licensed fork instead (Valkey, OpenTofu, FerretDB).
- Base layer / scratch images (alpine, debian, busybox, etc.) — we treat those as substrate, not products.
- Tutorial fixtures (bookinfo-*, examples-*, hello-world, demo-*, sample-*, test-image, dummy, tutorial, getting-started) are tracked but deprioritized in the "top image per project" selection — operators don't want to pull them.
If you ship a CE image of a project we're not yet tracking, file an issue with the project repo URL (github.com/c0inz/cerodeo/issues) and we'll add it.
If you think a grade is wrong
Three paths:
- The CVE itself is wrong (false positive, misattribution to your package, VEX statement available) — file with the upstream advisory source (GHSA, NVD, distro) and once it's withdrawn, the next hourly scan picks it up. We don't maintain our own vulnerability database.
- We mis-identified a package in your image — syft occasionally mis-detects. Open an issue with the image ref + a brief description; we can validate against the SBOM we extracted and work with the syft maintainers.
- You disagree with a weight in the rubric itself — the rubric is versioned (
cerodeo-v1today). Open an issue arguing for a change. If accepted, we ship a newrubric_versionand old snapshots stay auditable at both the old and new weights.
Everything else — Pulse grade (project health), publisher ranking, Scout Watch patch suggestions — is derived from the same raw inputs. If you fix the Scout inputs, the downstream surfaces catch up on the next scan.