Documentation
Getting started
Guides
On this page
Docs / Publisher trust
Publisher trust
Choose who may publish build data and verify the evidence available for Cache, Artifacts, and Registry.
Publisher trust across the products
A BoringCache signature records what the service accepted. A publisher attestation lets your own policy decide who produced the bytes. A checksum proves that bytes match a digest; it does not identify the producer.
| Product | Content check | Protected BoringCache read |
|---|---|---|
| Cache | Archive, OCI, GitHub Actions cache, and compiler/KV content digests. | Restore verifies the configured publisher for the exact manifest or KV batch subject under your pinned policy. |
| Artifacts | Representation SHA-256 and a BoringCache-signed custody receipt. | Native pull and the BoringCache GitHub Actions Artifact service verify a separate publisher attestation before returning content. |
| Registry | Manifest and blob digests. | boringcache docker pull verifies the publisher, then pulls the exact image digest. Standard Docker and OCI pulls do not use this policy. |
Require a publisher
Put the policy in .boringcache.toml. Choose the publisher method used by your CI. Both examples allow a push to one repository's main branch from one workflow. Replace every identity with values from your own run.
BoringBuild: pin the issuer's public RSA key from <issuer>/_boringbuild/oidc/jwks and the forge origin. If the JWKS endpoint requires operator access, obtain the public key from that operator before setting the protected policy pin. The signing job needs id-token: write. Set workflow-ref to the exact signed claim; its path can differ on another connected forge. The key ID, modulus, and exponent must identify the same key. Keep old keys in the policy while you still need to restore entries signed by them.
[trust]
version = 1
attestor = "boringbuild-oidc"
verifier = "boringbuild-oidc"
on-failure = "miss"
[[trust.publishers]]
type = "boringbuild-oidc"
issuer = "https://ci.example.com"
forge-origin = "https://github.com"
repository-id = "12345678"
source-refs = ["refs/heads/main"]
events = ["push"]
workflow-ref = "acme/widgets/.github/workflows/ci.yml@refs/heads/main"
public-key = { kid = "<JWKS key ID>", n = "<JWKS RSA modulus>", e = "AQAB" }
GitHub Actions: use a Sigstore bundle backed by Fulcio and Rekor.
[trust] version = 1 attestor = "sigstore" verifier = "sigstore" on-failure = "miss" [[trust.publishers]] type = "sigstore-keyless" issuer = "https://token.actions.githubusercontent.com" repository-id = "12345678" source-refs = ["refs/heads/main"] events = ["push"] workflow-ref = "acme/widgets/.github/workflows/ci.yml@refs/heads/main"
Keep the SHA-256 of the complete policy file in protected organization, workflow, or runner configuration that repository contributors cannot change. Pass it as BORINGCACHE_TRUST_POLICY_SHA256, or as the Action's trust-policy-sha256 input. A missing or changed policy then fails before restore. A restore-only job may omit attestor. Publication requires the selected CI issuer through the Action or boringcache ci run.
The same publisher list applies to Cache, compiler/KV, Artifacts, and protected Registry pulls. With on-failure = "miss", an unverified Cache or tool-cache entry is a miss and an unverified GitHub Actions Artifact is hidden. An explicit Artifact or Registry pull fails without approved evidence because it has no alternate entry to use. With on-failure = "fail", verification failures stop the operation. Cache values produced within the same local proxy process remain available to that process.
boringcache docker pull services/web:latest --workspace acme/widgets
Use BoringCache commands or the BoringCache GitHub Actions compatibility service for protected reads. A direct API request, standard docker pull, or another client that bypasses these channels must verify publisher evidence itself.
Verify evidence outside BoringCache
Cache: an authorized client can read attestation summaries in a cache restore response, then fetch one bundle from GET /v2/workspaces/:namespace/:workspace/caches/:cache_entry_id/attestations/:id. Decode its bundle_data base64 field. Fetch the exact manifest or pointer bytes from the restore request and hash them locally. Compare the subject kind and digest with the evidence before applying the configured issuer, repository, ref, event, and workflow policy.
curl -fsS \ -H "Authorization: Bearer $BORINGCACHE_RESTORE_TOKEN" \ "https://api.boringcache.com/v2/workspaces/acme/widgets/caches/$ENTRY_ID/attestations/$ATTESTATION_ID" \ | jq -r '.bundle_data' | openssl base64 -d -A > cache.sigstore.json shasum -a 256 cache.sigstore.json
The command above names a Sigstore bundle. For BoringBuild, save the decoded bundle as cache.oidc.jwt. Compare its hash with bundle_sha256 in the attestation summary. The restore response also contains the exact manifest storage request; keep its returned headers when downloading the bytes.
cosign verify-blob-attestation \ --bundle cache.sigstore.json \ --certificate-identity 'https://github.com/acme/widgets/.github/workflows/ci.yml@refs/heads/main' \ --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ manifest.bin
For Sigstore, read the verified in-toto statement as well. Its subject name must match the product kind, its digest must match the exact signed subject bytes, and its predicate type must be https://boringcache.com/attestation/publisher-subject/v1. The Cosign command checks the signature and subject digest; separately apply the repository ID, source ref, event, and workflow rules to the authenticated certificate. For BoringBuild, verify the JWT's RS256 signature with the public key pinned in your policy. Its aud must equal urn:boringcache:publisher:v1:<kind>:sha256:<digest>; also check iss, forge_origin, repository_id, ref, event_name, workflow_ref, boringbuild_source_trust = trusted, and boringbuild_ingress = forge. An unverified JWT payload is not evidence. Cache signs manifest or pointer bytes. KV signs a batch manifest that lists namespace, key, blob digest, and size. Artifacts sign the stored representation digest. Registry signs the OCI manifest digest. Authorized clients can fetch KV batch manifests from GET /v2/workspaces/:namespace/:workspace/cache-kv-publisher-batches/:id and Artifact or Registry bundles from GET /v2/workspaces/:namespace/:workspace/publisher-attestations/:id. Save the bundle, signed subject bytes, and pinned public key if verification must remain possible after access to BoringCache ends.
Artifacts: pull by immutable Artifact ID. The CLI checks the stored representation SHA-256 and extracted content digest before publishing the destination. The BoringCache GitHub Actions Artifact service downloads and verifies the complete Artifact before issuing a local download URL, which adds a wait before large protected downloads begin. The BoringCache-signed receipt records custody; the separate publisher bundle records the approved producer. A compressed directory's representation digest differs from the digest of its extracted files.
Registry: boringcache docker pull resolves a tag, verifies its signed manifest digest, and asks Docker to pull by that digest. Stock Docker and OCI clients can still pull without this policy. OCI referrers can hold other signatures, but their presence alone is not verification.
What the checks do not establish
A publisher attestation authenticates a subject digest and publisher. It does not bind a mutable tag or prove that the service returned the newest publication. A service can omit a cache entry or replay an older, correctly signed cache entry under a mutable tag. The same limit applies to a Registry tag that points to an older signed manifest. Require an independent record of the expected digest when freshness matters.
Public Sigstore transparency logs expose signing identity and subject digest metadata. BoringBuild OIDC evidence uses a pinned issuer key and has no public Rekor record. Decide which evidence and disclosure model your organization requires.
Publisher trust verifies the identity and signed subject. It does not establish that the build was reproducible, that dependencies were safe, or that the publisher's workflow was correct.
Need help or found something unclear? Open an issue or browse the CLI repo.