6.7.8 Signing and Verifying Artifacts

recommended

A successful signature check should answer a narrow question: did a trusted signing identity authorize this exact immutable subject? It should not merely confirm that some signature sits next to an artifact name. The release flow must therefore carry the subject digest across the build-to-signer boundary, keep signing credentials outside ordinary Bazel actions, and verify both the digest and the intended signer before promotion.1

Sign the subject, not its mutable name

6.7.4 From Build to Release Candidate establishes the input to signing: a technically eligible release candidate whose outputs are identified by cryptographic digest and whose required build evidence is complete. A tag such as stable, a version such as 2.4.0, and a filename such as server.tar help people find an artifact, but those names can be reassigned. They are references to the subject, not the subject itself.

A signing request should carry an explicit, reviewable tuple:

subject:
  media_type: application/vnd.example.release-archive
  digest_algorithm: sha256
  digest: 8b7...f21
signing_context:
  release_channel: production
  candidate_id: rc-1842

The exact wire format belongs to the selected signing system. The invariant is more important than the format: the signer authorizes a digest. If the release channel or candidate transition is meant to limit where that authorization can be replayed, bind the context into the signed payload or require it through verifier policy for that signed subject. Unsigned context stored only beside the request does not constrain reuse. The verifier recomputes or otherwise obtains the digest of the bytes it will consume, compares it with the signed subject, and evaluates the signer identity and the required context. Looking up server:stable, verifying a signature for a different digest, and then deploying whatever stable currently names breaks that chain.

OCI images make the distinction concrete. rules_oci supports digest-pinned image inputs and separates image construction from explicit registry operations. Its signing and attestation APIs are developer preview, so their current rule shape is evidence of one integration pattern, not a stable signing contract to standardize across every artifact type.2 Distroless provides a production case: its published images expose a Cosign verification path, while its internal Bazel release code keeps image composition, signing, and pushing as distinguishable parts of the release implementation.3

Put the signer after the cacheable build

The signing credential must not become an input, environment value, command-line argument, tool configuration file, or mounted secret of an ordinary cacheable action. If it does, the credential can cross into an execution request, remote worker, action log, cache object, or retained build evidence. It also couples a repeatable build to an authority-bearing side effect: a cache miss can now decide whether a release is signed.

Use the identity separation from 6.7.5 Build Identities and Secrets. The Bazel client and executors assemble the candidate. An eligibility service reconciles the result. A separate signer receives the immutable subject and only the evidence needed to authorize its signing request. The private key or equivalent signing authority remains behind that signer boundary. The returned signature or signed attestation is release evidence; it may be stored and distributed without giving the build access to the signing credential.

The existing identity and secret boundary contract makes the role split inspectable: the Bazel client, executor, provenance builder, signer, and publisher each have one allowed authority and explicitly deny adjacent ones. It is a deployment-neutral policy fixture, not a signer implementation; a real deployment must test its native IAM and retained surfaces. This separation is also a direct defense against user-defined build commands exfiltrating the signing key.1

This yields a useful retry boundary:

cacheable assembly -> eligibility -> signing -> verification -> publication

Rebuilding may reproduce or reuse the same candidate digest, but it does not sign or publish anything merely because bazel build ran. A signing outage leaves an eligible candidate unsigned rather than turning it into a failed build. A verification failure leaves it unverified rather than causing an automatic rebuild under the same potentially compromised path. 6.7.10 Publishing Artifacts Reliably then consumes the verified, immutable handoff without confusing publication retries with build retries.

Signing an attestation follows the same boundary but authorizes a different object. The signed payload contains claims about a subject; the verifier must check the payload's signature and confirm that the payload names the digest being promoted. Attaching an SPDX statement with Cosign, as the rules_oci example does, transports a supplied statement. It does not generate that statement or prove that its component inventory is complete.2 6.7.6 Build Provenance and Attestations covers which build claims can be supported before they are signed.

Verification is a policy-bearing operation

Cryptographic validity is necessary but insufficient. A verifier needs an explicit trust input and an expected subject. The trust checks depend on the signing scheme:

  • with a fixed key, configure the exact expected verification key or trust anchor;
  • with keyless signing, require the certificate identity and its OIDC issuer, then validate that certificate path under a configured trust root.3

An identity and a trust root are not interchangeable alternatives. The identity says who the verifier is prepared to accept; the configured key or trust root establishes the authority under which the signature or certificate is authenticated.

At minimum, the verifier record should retain:

  • the digest and digest algorithm of the consumed artifact;
  • the signature and signed payload or envelope that were checked;
  • the scheme-specific verification material, or an immutable retained locator for it;
  • the authenticated signer claim and the configured key or trust root actually used;
  • the intended release context, when the signature is scoped by one;
  • the verifier and trust-configuration versions; and
  • a resolved result that downstream promotion can consume.

For example, a current Sigstore bundle retains the signed message together with scheme-specific material such as the signing certificate and transparency-log or timestamp evidence. The verifier still applies its configured Sigstore trust root and expected certificate identity plus OIDC issuer; the bundle is evidence, not the policy.

This record prevents a green verify step from becoming an unexplained Boolean. It also lets an incident responder answer whether a later trust-policy change affects a past promotion. The build graph alone cannot answer that question: Bazel produced the subject, but the signer and verifier establish a separate authority relationship. Authenticating the signature, binding its payload to the artifact digest, and checking the accepted signer or policy are distinct verifier steps.1

Use explicit outcomes. accepted means the subject, signature, signer, and required context all agree under the named trust configuration. rejected means available evidence contradicts one of those requirements. unknown means required signature or trust evidence is missing, inaccessible, or cannot be evaluated. Both rejected and unknown block promotion, but preserving the distinction tells an operator whether to investigate hostile or contradictory evidence, or restore an unavailable verification dependency. These are local workflow outcomes, not SLSA or Sigstore protocol states.

Choose one of two integration shapes deliberately:

  1. Detached release stage. The release orchestrator hands a digest-identified candidate to a signer and later to a verifier. This is the default for shared infrastructure because the credential and side-effect boundary is visible and can use a dedicated workload identity.
  2. Ruleset-provided runnable operation. A supported ruleset may offer an explicit target invoked with bazel run for signing or attestation. Treat it as a release operation, not as proof that the signing key belongs in the action graph. Check the API's stability and credential path for the pinned ruleset version; rules_oci currently labels this surface as developer preview.2

The second shape can reduce integration code for one artifact ecosystem. It does not remove the need for separate identities, digest binding, negative tests, or an independent verification decision.

Test substitution, replay, and missing trust

A happy-path signature proves little about the system boundary. Start with one known candidate and preserve its digest, signature, signer identity, and verifier result. Then mutate one condition at a time:

TestRequired outcome
Replace the artifact bytes but retain the filename or tagVerification fails because the consumed digest no longer matches the signed subject.
Present a valid signature for another candidateVerification fails the expected-subject check.
Present a valid signature from an untrusted identityVerification rejects the signer even though the signature is mathematically valid.
Remove required trust evidence or make it unavailable to the verifierVerification returns unknown, and promotion stops rather than silently accepting the artifact.
Replay an old signed candidate into a newer release contextContext or expected-candidate checks reject it, unless the workflow explicitly permits that reuse.
Reuse evidence from a signer whose authority was withdrawnCurrent verifier policy rejects that signer and blocks new promotion.
Make signing unavailable after build eligibilityThe candidate remains eligible and unsigned; no build result is rewritten.

The first two cases distinguish byte identity from mutable naming. The third distinguishes cryptographic integrity from authorization. The replay case forces you to state whether a signature authorizes a subject everywhere, or only for a release channel, destination, or candidate transition. That is an organization policy choice, but the infrastructure must carry enough context to enforce the chosen answer.

The runnable verifier contract tests exercise accepted, rejected, and unknown across those mutations, including a revoked signer. The fixture verifier recomputes the consumed digest and applies fixed-key or keyless policy. Its checked-in signature_verified input deliberately stands in for a real cryptographic verifier: the snippet proves the policy-state transitions and subject binding, not certificate validation, key custody, transparency-log verification, or immutable evidence storage.

Bound a signer compromise

Withdrawing a compromised signer's authority stops future acceptance only after verifiers receive and enforce the changed trust state. The mechanism is system-specific: change the authorization or trust input that the verifier actually enforces, not merely an inventory entry beside it. That change does not retroactively repair artifacts, signatures, or attestations already produced under the compromised authority.1 Prepare a digest-indexed response:

  1. disable new signing and preserve signer, request, subject, and time evidence;
  2. enumerate every affected subject and release transition attributed to the signer;
  3. quarantine those subjects from new promotion while keeping unrelated builders and signers available;
  4. rebuild candidates through an independently trusted builder when build integrity is in doubt;
  5. sign the resulting digests with a replacement authority; and
  6. update verifier trust state and confirm that the compromised identity is rejected.

Do not automatically assume that signer compromise changed artifact bytes; signing and building are separate roles. But do not accept the old signature as evidence of authorization after its trust basis has been withdrawn. The retained subject and identity records determine which releases need investigation, rebuild, or re-signing.

think

Trace: A deployment job verifies a valid signature for digest A. It then resolves the mutable tag production, which now points to digest B, and deploys B. Which boundary failed?

Reveal

The verifier proved authorization for A, but the consumer never checked that the bytes it promoted were A. The repair is not merely to verify the tag again. Resolve the candidate to an immutable digest, verify that exact subject under the required signer and context, and pass the same digest to publication and deployment.

key takeaway

Signing is a separate authority-bearing stage that consumes the immutable digest from an eligible Bazel-built candidate. Keep signing credentials outside cacheable actions, retain the signature or envelope and its scheme-specific verification material, and require downstream consumers to use the exact digest whose signature was accepted. A fixed-key policy names the expected key or trust anchor. A keyless policy requires the certificate identity and OIDC issuer under a configured trust root. A mutable tag or filename may locate a subject, but it cannot replace subject identity.

Prove the boundary with substitution, wrong-subject, untrusted-signer, missing-trust, and replay tests. If a signer is compromised, withdraw future authority, enumerate affected digests from retained evidence, rebuild where build integrity is uncertain, re-sign through an independent authority, and verify that the old identity is rejected.

Check your understanding · 4 questions

1.When may a verifier accept an older signature for the same artifact digest in a new release context?

Select one answer

2.Which controls keep signing separate from ordinary cacheable Bazel assembly?

Select all that apply

3.Match each verification or incident case to its required disposition:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
Artifact bytes no longer match the signed digest
Valid signature names a different candidate digest
Required trust evidence cannot be evaluated
The accepted signing authority is compromised

4.Which verifier trust configuration correctly distinguishes fixed-key from keyless signing?

Select one answer

0 of 4 answered

Footnotes

  1. Building Secure and Reliable Systems — Ch. 14: Deploying Code — accepted-signer policy, artifact binding, signer-key protection, and privilege separation from user-defined build commands 1 2 3 4

  2. rules_oci — daemonless OCI image rules for Bazel — digest-pinned OCI inputs, the build-versus-registry boundary, and developer-preview Cosign signing and attestation examples 1 2 3

  3. Distroless — minimal runtime images built with Bazel — published keyless signature-verification route and internal separation of image composition, signing, and pushing 1 2