Skip to content
CommonLayer
Documentation menu
On this page

Verification policy

Every container image and Helm chart in the CommonLayer registry was submitted by its project's CI as a digest, fetched from the source registry, and checked against the verification policy before it was copied and promoted. This page lists the checks, how a check can be waived, how projects can tighten the policy, and what the result does and does not say.

The policy and each check's result are public: GET /api/v1/verification-policy returns the platform default, and GET /api/v1/digests/{digest} returns the checks recorded for a digest. The implementation is crates/commonlayer-artifacts/src/domain/policy.rs; every check has a passing and a failing test (policy_tests.rs).

Results

Each check yields pass, fail, waived (by a recorded exception) or not_applicable (for example chart checks on an image). Promotion requires that no check fails. A rejected submission keeps its digest record in state rejected with the failed checks, so the public digest page explains why. For a failed scan gate the public page states only how many findings blocked it, never their ids, because a finding may be embargoed (security review MEDIUM-7).

Resuming a failed submission (re-submitting the same digest) runs the scan, the evaluation, mirroring and promotion again; an old evaluation is never reused (MEDIUM-2).

Checks

CheckRequirementWaivable
source.matches_manifestThe source registry and repository equal registry.source in the project's commonlayer.yamlnever
digest.verifiedThe fetched content hashes to the submitted digestnever
version.declaredThe version belongs to a supported line in the manifest, and the revision is newnever
platforms.completeEvery required platform is present (default linux/amd64 and linux/arm64)by platform waiver
image.nonrootThe image config user is a non-zero UIDby manifest deviation non-root or platform waiver
image.labelsThe required OCI and org.commonlayer.* labels are present and consistent with the submissionby manifest deviation labels or platform waiver
signature.validAt least one cosign signature verifiesnever
signature.identityThe signer's identity and issuer match the identity registered in the manifest (keyless) or a registered public keynever
sbom.presentA signed SBOM attestation in an accepted format exists for every platformby platform waiver
sbom.parsableThe SBOM parses and lists componentsby platform waiver
provenance.presentA signed SLSA provenance attestation existsby platform waiver
provenance.subjectThe provenance subject digest equals the artifact digestby platform waiver
provenance.sourceThe provenance source repository is a verified repository of the project, and the commit matches the submissionby platform waiver
scan.freshA successful scan by at least one scanner completed for this digestby platform waiver
scan.gateNo finding at or above the gate severity (default critical) that has a fix available, unless covered by an approved exception or a not_affected statementexceptions and VEX come from the security domain
chart.images_pinnedA chart's default images are digests of promoted CommonLayer imagesby platform waiver
chart.schemaA chart ships values.schema.jsonby platform waiver

Charts are not image-scanned: scan.fresh and scan.gate are not_applicable for a chart, whose images must already be promoted, scanned CommonLayer digests (chart.images_pinned).

What has been exercised. All 17 checks have unit tests with a passing and a failing case. The publish drill (WP-70g, locally, with real cosign, Trivy, Grype and a real registry) produced 15 passing image checks and 10 passing chart checks for correctly built artifacts signed with a key, and a rejection listing 8 failed checks for an unsigned image. signature.identity for keyless signatures, and provenance.* for provenance made by GitHub's attestation action, have not been exercised outside unit tests.

Required labels by default: org.opencontainers.image.{title, description, version, revision, created, source, url, documentation, licenses, vendor, base.name, base.digest} and org.commonlayer.{project, variant, upstream.version, revision, standard} (DEFAULT_REQUIRED_LABELS).

Waivers

A failing check becomes waived only through one of two recorded exceptions (ADR-0028):

  1. A manifest deviation for the image standards non-root (check image.nonroot) and labels (check image.labels). Deviations are declared in commonlayer.yaml with a reason and shown on the artifact page.
  2. A platform waiver in the platform policy, recorded by a holder of platform.settings.manage through PUT /api/v1/verification-policy. A waiver names the check, the project, optionally the artifact, a reason of at least 20 characters, who recorded it and when, and an optional expiry. Publishing a new platform policy version is the only way to record a waiver; it is audited.

The checks that establish what an artifact is and who built it (source.*, digest.verified, version.declared, signature.*) can never be waived.

Project policies

A project admin (project.settings.manage) can set a project policy (PUT /api/v1/projects/{slug}/verification-policy) that only tightens the platform default: a stricter scan gate, more required platforms or labels, fewer accepted SBOM formats. A project policy cannot carry waivers and cannot switch provenance off.

What the policy does not claim

  • A valid keyless signature proves which workflow, repository and ref produced the digest. It says nothing by itself about whether the content is secure.
  • A passing scan means the configured scanners found nothing at or above the gate with a fix available, at the time of the scan, with their databases of that day. Scanners only see what the SBOM lists. Projects declare the software they build from source (nginx, Valkey, PostgreSQL, darkhttpd) in the SBOM with CPEs; Grype matches those declarations, Trivy does not, so for that software the result rests on one scanner (ADR-0039). CommonLayer's own helpers (cl-probe, cl-kube-label) are listed but have no vulnerability feed, and libraries vendored into a binary surface only as that binary's vulnerabilities. Scans are repeated daily for supported digests and SBOMs are re-matched against OSV every six hours, so findings can appear after promotion.
  • A key-signed artifact's signature proves that the holder of the registered key signed it, not which build produced it; the provenance attestation is the evidence for that.
  • CommonLayer does not claim a SLSA level for any artifact and does not call an image "hardened" because a scan passed. It states what it verified.

Snapshot and instructions

Each verification stores the effective policy (platform plus project) and the signing identity in force. GET /api/v1/digests/{digest}/verify-instructions builds the exact commands a consumer can run from that snapshot; see verifying artifacts.

Configuration that affects verification

SettingEffect
registry.cosign_pathPath of the cosign binary. Without it, submissions stop with artifacts.verifier_not_configured; verification is never skipped
registry.cosign_require_transparency_logRequire a Rekor entry (must be true in production; false only in development and test, as in the publish drill)
registry.allowed_source_registriesRegistries a submission may name (default ghcr.io and the local source registry)
security.trivy_path, security.grype_path, security.osv_enabledWhich scanners run; at least one must succeed for scan.fresh
security.gateDefault gate severity for the scan gate in the platform's security domain
security.max_scan_age_hours (runtime setting, default 48)How old a successful scan may be and still count as fresh
registry.scan_timeout_secsHow long the pipeline waits for a scan (default 2 h); a timeout ends the submission as failed (resumable), not rejected