Skip to main content
Menu

Zero CVE on release — what it actually means, and where its limits are

Published · Rasid

“Zero CVE” is a phrase that has lost some calibration. This post says what it means when Rasid puts the phrase on an image release, what the daily-rebuild pipeline does to keep it true over time, what the advisory.json feed exposes to your downstream scanner, and — most importantly — where the limits of the guarantee sit.

The short answer: at the moment of build, the image carries no advisories from the union of NVD + OSV + Wolfi + upstream-project security feeds. Between that moment and “right now”, new advisories can appear. The daily rebuild and the advisory feed are how we close the gap. Your scanner is how you see the gap close on your end.

The rest of the post is the long answer.

What we scan against, and when

Three feeds, merged on a refresh cadence:

  • NVD — the U.S. National Vulnerability Database, the authoritative CVE record. Refresh: hourly.
  • OSV — the Open Source Vulnerability database, with broader coverage of language ecosystems (npm, PyPI, Cargo, Go modules) than NVD’s CPE-centric model. Refresh: hourly.
  • Wolfi advisory feed — the upstream advisory feed for the Wolfi undistro, which is the apk-based base our images derive from. Refresh: continuous; we pull on every push to the upstream advisory branch.
  • Project-specific feeds — for the images whose upstream projects publish their own security advisories (Postgres, Nginx, Node), we additionally pull those feeds directly. Refresh: hourly.

At build time, the scanner walks the resulting unioned advisory set against the package manifest of the image. If any advisory matches an installed package version, the build fails — the image does not get a signature, does not get an SBOM published, does not get pushed to the registry.

That is the “zero CVE on release” floor. Mechanically: at the moment the registry accepts the push, no advisory in the merged feed matches a package in the image.

What that does NOT cover

Five things, in rough order of how often they bite.

1. CVEs published after the build, before the next rebuild

The window between “we built and released” and “an advisory dropped against a package in the image” is the central limit. If a CVE was unknown to NVD, OSV, Wolfi, and the upstream project at 02:00 UTC when we built, and CVE-2026-12345 publishes against a Postgres extension we ship at 14:00 UTC, the image was zero-CVE at release and is now a one-CVE image until the next rebuild absorbs it.

The daily-rebuild loop is the structural answer to this. Every image in the catalogue is rebuilt every 24 hours against the latest advisory data, even when no recipe changed. Patch latency is bounded above by ~24 hours plus your pull cadence.

For workloads that cannot wait 24 hours, the advisory.json feed is the polling-grade signal — see the next section.

2. The advisory.json feed

For every image at every digest, Rasid publishes a structured advisory document at a predictable URL:

https://images.rasid.cc/.well-known/advisory/<registry-path>/<digest>.json

The schema:

{
  "image": "images.rasid.cc/wolfi/nginx",
  "digest": "sha256:abcd1234...",
  "built_at": "2026-05-29T02:14:33Z",
  "scanned_at": "2026-05-30T14:00:00Z",
  "feeds_consumed": {
    "nvd":  { "high_water_mark": "2026-05-30T13:50:00Z" },
    "osv":  { "high_water_mark": "2026-05-30T13:50:00Z" },
    "wolfi":{ "high_water_mark": "2026-05-30T13:45:00Z" },
    "upstream:nginx": { "high_water_mark": "2026-05-30T12:00:00Z" }
  },
  "advisories": [
    {
      "id": "CVE-2026-12345",
      "severity": "high",
      "package": "postgresql-15-pgcrypto",
      "fixed_in_digest": "sha256:beef5678...",
      "fixed_in_tag": "15.7-r2",
      "mitigation": "Pin to digest sha256:beef5678... or pull the :15.7-r2 tag built after 2026-05-30T14:30:00Z."
    }
  ]
}

Two things the schema makes ergonomic:

  • A scanner can fetch the advisory.json for the digest it currently has deployed and learn whether any new CVE has been published against that exact build since the SBOM scan was last run. No re-scanning the SBOM from scratch.
  • The fixed_in_digest field is the precise pointer to the rebuilt image that no longer carries the advisory. A platform team can pin to that digest deterministically — no tag-resolution ambiguity.

The full schema spec lives at docs/architecture/build-pipeline.mdx. We treat the schema as a stable contract; field additions are non-breaking and removal will go through a deprecation window.

3. Edge: a CVE that has no upstream fix

Some CVEs are real and have no patched upstream version yet. The advisory.json records these with fixed_in_digest: null and a mitigation field that tells you what we can offer — a configuration change, a build flag, a disabled feature, or an honest “no mitigation yet; the upstream project is working on it.”

This is the case where the “zero CVE” framing is least useful. The image cannot be zero-CVE because no fix exists. What Rasid offers in this state is: honest disclosure, fast mitigation guidance when there is one, and a same-day rebuild the moment the upstream patch lands. The runbook for what to do in this state lives at docs/runbooks/incident-response.mdx.

4. Edge: a CPE-match false positive

NVD’s CPE matching is conservative. It will sometimes flag a package as affected by a CVE when the actual code path is not present in the build — for instance, a CVE against openssl/openssl 3.x with certain ENGINE configuration will match every openssl 3.x build even when the vulnerable ENGINE module is not compiled in.

Rasid does not silently suppress false positives. The advisory.json records the match with a disposition field that can be affected, not_affected_no_code_path, not_affected_mitigated_by_config, or under_investigation. Each non-trivial disposition includes a rationale pointing to the build-recipe evidence. The triage is auditable; if your auditor disagrees with our not_affected call, the rationale is the artefact they can read.

5. Edge: a CVE in a dependency Rasid does not ship

Your application has dependencies. We do not. We ship the base. The CVE in your package.json is not in the advisory feed for the Rasid Node image, because it is not in the Rasid Node image — it is in your image, which is layered on top of ours.

The SBOM Rasid ships is the SBOM of the base. Your scanner needs to walk your application layer on its own. The composition of “the base’s SBOM” and “your app layer’s SBOM” is the deployment-time SBOM, and that is the artefact admission control should evaluate against.

How the daily rebuild works (the mechanics)

Each image in the catalogue is a recipe — a small declarative description of the package set, the Wolfi APKINDEX revision, the entrypoint, and any image-specific configuration. The build pipeline:

  1. Pulls the latest Wolfi APKINDEX (which includes the latest packages with the latest advisories absorbed).
  2. Runs the recipe against the new APKINDEX. If the package set resolves cleanly, the image rebuilds.
  3. Scans the resulting image against the merged advisory feed.
  4. If the scan returns zero unmitigated advisories, the image is signed, SBOM-attested, SLSA-attested, and pushed.
  5. If the scan returns advisories with no fix in the current APKINDEX, the build records the open advisories in the advisory.json, the image is not signed, and the previous-known-good digest remains the head of the tag stream. We do not ship an image that fails the floor.
  6. Notification fires to the affected image’s maintainer for follow-up — typically waiting for the upstream patch and rebuilding when it lands.

The pipeline runs once every 24 hours per image, plus on-demand whenever a high-severity advisory matches the catalogue. The on-demand path is what keeps the patch-latency tail bounded for the cases that matter most.

How to consume this in your scanner

Two patterns work well.

Pattern A — re-scan on a schedule, source of truth is the SBOM. Your scanner pulls the SBOM from the OCI referrer, walks it against its own advisory feed, and decides. Treat Rasid’s “zero CVE on release” as a starting condition; let your own scanner re-establish ground truth as your scanner’s feed updates.

Pattern B — poll the advisory.json on a schedule, source of truth is Rasid. Your scanner fetches <image>@<digest>.json, reads the advisories array, and acts on it. This is the lower-overhead pattern when you trust Rasid’s scan to be at least as comprehensive as your own.

Most platform teams run both — A for the application layer, B for the base layer — and reconcile in the alert pipeline. Either is fine. The advisory feed exists so that you don’t have to pick.

A closing honesty

“Zero CVE on release” is a useful floor. It is not a substitute for the work a platform security team does every day: triage, mitigation, exception management, the inevitable cycle of CVEs that affect your specific deployment in ways the generic feed cannot anticipate.

What we promise is the floor, mechanically verifiable, daily-refreshed, with the gaps documented. What we don’t promise is that you can stop thinking about security because you pulled a Rasid image. We’d rather be useful than wave a marketing flag.

— Rasid