TRUST AND VERIFICATION

HOW TO VERIFY A VERITY IMAGE

This page is for the person who has to decide whether to trust an image pulled from verity.supply. It describes what the publication gate actually checks, how images are signed, how the SBOM and build provenance are produced, and gives runnable commands to verify all three yourself. Every claim below comes from docs/POLICY.md, docs/COMPLIANCE_AUDIT.md, and docs/catalog.schema.json in this repository. Where this page and the raw data disagree, trust the raw data at catalog.json.

PUBLICATION GATE

ZERO FIXABLE FINDINGS, NOT ZERO FINDINGS

The number on a catalog row and the pass/fail decision are two different things.

Grype scans every build candidate before it is pushed to a registry and records every known candidate vulnerability it finds, at any severity, on any track. Unfixed findings are not discarded: they remain in the scan report and in the published catalog entry. The gate itself only inspects one number, the count of findings that have a fix available. An image fails if that count is anything other than zero. A patched image may publish only when its final scan also shows zero fixable findings; wolfi images are held to the same zero-fixable bar. No image is admitted on the basis of improvement alone: a lower vulnerability count than the upstream base is evidence for diagnosis, not an exception to the gate.

That is why a catalog row can legitimately show a nonzero finding count and still have passed the gate. A row reads 0 FINDINGS when the recorded scan contains no findings of any kind. Otherwise it shows the worst recorded severity and total count, such as HIGH 6; expanding the row reveals the full ALL FINDINGS breakdown by severity. Every published image satisfies the same rule: zero reported findings have an available fix. A nonzero count does not fail this publication gate; it means vulnerabilities were detected but none were fixable at scan time, so none blocked the release. The image is not free of known vulnerabilities.

Two scopes are worth separating too. The gate is evaluated once, against the Grype vulnerability database as it existed at scan time, immediately before publication. After publication, a separate monitoring workflow stores each platform SBOM once and compares its components against incremental OSV updates; if a fix later becomes available for something already published, that workflow opens or updates a tracking issue for the affected image version. It does not pull image layers, rescan the image, rebuild it, or alter the published catalog entry. The count in the catalog is therefore a statement about the scan performed at publish time, not a live, continuously reevaluated guarantee.

SIGNING

KEYLESS COSIGN SIGNATURES

Every image index is signed by digest. There is no long-lived private key to steal.

Signing uses cosign's keyless flow: the build workflow authenticates to Fulcio with GitHub's short-lived OIDC identity and signs with a certificate that expires almost immediately, rather than a stored private key. The workflow's only credential is the ambient GITHUB_TOKEN; it requires no personal access token and no stored signing secret. Verification checks the certificate's identity and issuer instead of a public key.

Use Cosign 3.0.6 or newer. Older verifiers, including 2.6.1, fail against these signatures and attestations because of an OCI storage-format difference, not because the artifacts are missing.

SBOM

SPDX JSON, ONE PER PLATFORM

Every published digest carries a software bill of materials in a single format.

SPDX JSON is the only SBOM format the catalog produces; there is no CycloneDX equivalent. Wolfi images use apko's native SPDX output. Patched images use Syft, run against the final patched image rather than the pinned upstream. Trivy is used only to produce the Copacetic patch report for patched images and never generates an SBOM. One complete SBOM is attached to each platform's image digest as an OCI attestation, signed the same way the image itself is signed, and can be verified or downloaded directly from the registry. Melange-backed Wolfi images additionally retain package-level provenance and a metadata subpackage containing the resolved go.mod and go.sum used by the build.

BUILD PROVENANCE

SLSA PROVENANCE FOR EVERY DIGEST

Provenance ties a published digest back to the workflow run that built it.

Every pushed digest receives SLSA build provenance from actions/attest-build-provenance plus a GitHub artifact attestation. Wolfi builds use and upload the reviewed, checked-in apko lock as a build input. Melange-backed builds are the exception: because their package signature is ephemeral, each run produces a build-specific lock, which is uploaded together with the recipe, package provenance, and resolved dependency metadata. Patched builds instead use the checked-in source digest and record it in immutable build metadata. In every case, upstream or Wolfi package updates require a pull request that changes the pin, recipe, or lock, so the inputs behind a given provenance statement were reviewed before the build ran.

WORKED EXAMPLE

VERIFY AN IMAGE IN THREE COMMANDS

Substitute the digest from the catalog row you actually pulled.

Every row in the catalog exposes these same three commands, already filled in with that row's registry reference. The example below uses caddy, one of the images in the Wolfi track, pulled from verity.supply instead of a mutable tag:

docker pull verity.supply/caddy@sha256:DIGEST
SIGNATURE cosign verify --certificate-identity 'https://github.com/tektum/verity-images/.github/workflows/build.yaml@refs/heads/main' --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' verity.supply/caddy@sha256:DIGEST
SBOM cosign verify-attestation --type spdxjson --certificate-identity 'https://github.com/tektum/verity-images/.github/workflows/build.yaml@refs/heads/main' --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' verity.supply/caddy@sha256:DIGEST
PROVENANCE gh attestation verify oci://verity.supply/caddy@sha256:DIGEST --repo tektum/verity-images

The signature command fails closed if the certificate identity or issuer does not match exactly, so a signature produced by any workflow other than build.yaml on refs/heads/main is rejected rather than silently accepted. Pin the digest, not the tag: tags such as :latest or :<version> are mutable discovery aids and can move.

SOURCE OF TRUTH

WHERE THE RAW DATA LIVES

The catalog UI is a rendering of these two files. Nothing on this site is authoritative on its own.

CATALOG.JSON HOLDS EVERY IMAGE, DIGEST, SCAN RESULT, AND VERIFICATION COMMAND.

CATALOG.SCHEMA.JSON DEFINES THE SHAPE EVERY ENTRY MUST MATCH, INCLUDING THE ZERO-FIXABLE POLICY, SBOM FORMAT, AND SIGNING IDENTITY.

OPEN CATALOG.JSON OPEN CATALOG.SCHEMA.JSON

The schema fixes several values as constants rather than free text: schemaVersion is 2, policy.fixableVulnerabilitiesAllowed is 0, policy.sbomFormat is SPDX-JSON, policy.cosignMinimumVersion is 3.0.6, and every per-image scan object must report exactly 0 fixable findings, whatever its total finding count is. Each image entry also carries its own verification.signature, verification.sbom, and verification.provenance commands, generated directly from that image's registry reference rather than typed by hand.