6.7.6 Build Provenance and Attestations
recommendedA build graph can explain which declared inputs Bazel used to construct an output. It cannot, on its own, tell a downstream consumer who operated the builder, whether the recorded invocation was the approved one, or whether the bytes being inspected are the output that record describes. Build provenance closes that handoff by binding an immutable artifact digest to explicit claims about the source, resolved materials, invocation, configuration, and builder.
The useful question is not “do we have provenance?” It is “which claims can a verifier establish from this record, under which trust assumptions?”
Build an evidence join, not a Bazel graph export
Start with the release subject established in 6.7.4 From Build to Release Candidate. For each output that may advance through the release pipeline, provenance needs a digest-identified subject. A mutable filename or tag can help a person find the output, but the digest is what joins the claim to the bytes.
Then connect five dimensions:
| Dimension | Question the record must answer | Typical Bazel-aware evidence |
|---|---|---|
| Subject | Which exact output bytes does this claim describe? | Candidate manifest and output digest |
| Source and materials | Which immutable source revision and resolved build inputs contributed? | Verified source revision, lock state, repository-resolution records, toolchain and input digests |
| Invocation and configuration | What build was requested and which configuration produced the subject? | Intended targets, Bazel version, flags, platforms, configuration identifiers, invocation ID |
| Builder | Which trusted build platform made the claim? | Authenticated CI/build-service identity and its documented trust boundary |
| Supporting evidence | Where can an investigator test or qualify the claim? | Complete-result record, BEP/BES invocation, logs, profiles, and retained artifacts |
This is a join across evidence systems, not a serialization of bazel query,
cquery, aquery, or the BEP. The configured and action graphs are valuable
evidence about the work Bazel modeled. BEP/BES supplies invocation and result
evidence. The artifact store supplies durable bytes and digests. The CI control
plane supplies the authenticated builder and source context. None of those
surfaces independently supplies the whole statement.
The current SLSA build-provenance model gives this join a useful standard shape.
An in-toto statement binds subject digests to the SLSA provenance predicate.
Its buildDefinition separates a versioned buildType, externally controlled
parameters, internally controlled parameters, and resolvedDependencies; its
runDetails identifies the builder and the particular invocation. In the current
SLSA v1.2 documentation, the stable predicate type remains
https://slsa.dev/provenance/v1—the documentation path's v1.2 is not a new
predicate URI.1 Treat
that schema as an exchange contract, not as evidence generation: populating a
field does not prove its value.
Preserve Bazel's distinct input layers
Avoid flattening every input into a list called “dependencies.” A verifier needs to know why an input appears and how strongly its identity was established.
- Source is the immutable revision containing the build definitions and first-party code. Record the revision the trusted builder fetched, not merely a branch or tag supplied by the caller.
- External parameters are caller-controlled choices that can change the build, such as the requested release entry point or an allowed configuration selector. A verifier must compare these with expectations rather than trusting them because they appear in a signed record.
- Internal parameters describe choices made inside the trusted builder. They help reproduction and incident response, but their trust comes from the builder boundary that recorded them.
- Resolved dependencies identify observed artifacts needed at build time:
source commits, dependency archives, toolchains, generated repository inputs,
and artifacts fetched during initialization or execution—including transitive
fetches where the integration can observe them. Current SLSA calls this field
resolvedDependencies; older provenance vocabulary often called these materials.1
This separation matters in a Bazel build because repository resolution, module
extensions, toolchain selection, configuration, and action execution expose
different evidence. A MODULE.bazel dependency declaration is not proof of the
archive bytes fetched. A lockfile improves the identity record, but it does not
automatically enumerate every file that an opaque script downloaded. Likewise,
an action graph describes declared action inputs; it does not prove that an
untrusted wrapper, repository rule, or tool did not consult an undeclared host
resource.
External dependency networks also grow transitively and change outside the organization's control. Security-driven upgrades and orphaned ownership make it especially important that materials remain traceable rather than collapsing to one top-level version label.2 When a Bazel or ruleset integration cannot observe a dependency reliably, record that dimension as incomplete. Do not invent completeness by copying declarations into the provenance. Even SLSA Build L3 treats completeness as best effort: the record must state the producer's measurement boundary rather than imply omniscience.1
Make builder identity carry an explicit trust boundary
The builder is not simply the process that invoked bazel. It is the trusted
platform responsible for faithfully executing the build and producing the
provenance. Its identity should distinguish modes with materially different
security properties—for example, a hardened release worker pool from a
self-hosted development runner.
Document what each builder identity actually guarantees:
- how source revisions and invocation parameters are authenticated;
- which fields the trusted control plane measures and which the workload merely reports;
- whether local execution, remote execution, and cache hits are allowed;
- which cache writers and executor populations may contribute results;
- how provenance is associated with complete build evidence; and
- which logs and artifacts remain available for investigation.
This is where 6.7.5 Build Identities and Secrets becomes essential. The builder identity names the claimant; the attestation signer authenticates the record. Those are related but not interchangeable roles. A signer can authenticate a claim for more than one builder, and an executor that ran an action is not automatically authorized to sign release provenance.
Remote cache hits need the same restraint. CAS digests let a client check that downloaded bytes match the requested content address, but an ordinary action cache mapping does not by itself prove which trusted party produced that result.3 If the release builder accepts cache entries from a broader population, the provenance must not imply that every contributing action executed inside the release builder's hardened environment. Either constrain the admissible cache writer population, rebuild under the required trust boundary, or state the weaker guarantee.
State what the record does not prove
A precise provenance record can support a strong claim and still leave important questions unanswered. Keep these limits visible to consumers:
- A source URI and commit digest identify source; they do not prove code review, branch protection, or that the source is benign.
- Resolved dependencies describe observed build materials; they do not prove a complete SBOM or the exact runtime composition of a package. That distinction belongs to 6.7.7 Generating SBOMs from Bazel Builds.
- Builder identity attributes a claim to a trust boundary; it does not prove that the platform was uncompromised. Trust in that platform is a verifier policy input.
- Invocation parameters describe a requested build; they do not prove that the request complied with organizational policy. Policy evaluation is developed in 6.7.9 Bazel Policy and Compliance Checks.
- A successful Bazel result proves neither release approval nor publication.
- A provenance statement is not automatically authentic. Its envelope and subject binding must be verified against an accepted signer-builder pair; 6.7.8 Signing and Verifying Artifacts develops the signing boundary.
Machine location is a good negative test. A log may report a worker hostname or
region, but a standard provenance statement need not prove the physical location
of execution. If residency matters, it needs separately defined, trustworthy
evidence and policy. Adding a free-form region field supplied by the workload
does not create that proof.
Verify the claim from the consumer's side
Test provenance as a verifier would, not by checking that the producer emitted JSON. Given an eligible candidate, the verification path should:
- calculate the digest of the artifact being considered and require a matching subject;
- authenticate the attestation envelope and accept only an expected signer-builder pairing;
- require the expected predicate and build type;
- compare source identity and externally controlled parameters with release expectations;
- trace resolved materials and invocation evidence as far as the producer's documented completeness guarantee allows; and
- reject or quarantine evidence that contradicts an asserted complete claim,
and return
unknownwhen a required evidence dimension is explicitly incomplete.
Now seed negative cases. Attach valid provenance to different bytes. Replace the source revision while retaining a mutable tag. Generate a record on an unapproved runner. Change one external parameter. Remove one required material. Accept an action-cache result from an identity outside the release trust boundary. Finally, omit execution location and confirm that the verifier does not infer it.
Each test should fail for a specific reason. “The JSON parsed” and “the build was
green” are not verification results. The purpose of the exercise is to show that
the record supports bounded decisions and preserves unknown where the evidence
cannot justify a stronger conclusion.
The bounded provenance verifier makes the decision concrete. Its policy checks first consume the result of an upstream envelope verifier: authentication must have succeeded, the signer must be trusted, and policy must allow that signer to attest for the named builder. The fixture does not implement cryptographic signature verification; its authenticated-envelope result is the explicit handoff from that external mechanism. It then recalculates the subject digest, constrains predicate and build type, and checks the dependencies the build type says it can observe.
The top-level evidence.complete marker is fixture policy metadata, not a SLSA
v1 predicate field. When false, it dominates a missing dependency as unknown;
when true, the same omission contradicts the completeness claim and is
rejected. Independent contradictions—authentication, signer-builder policy,
subject, predicate, or build type—still reject. The mutation suite
tests each boundary and the combined incomplete-plus-missing case.
Running PYTHONPATH=. python3 tools/verify_cases.py inside that project produces:
complete: accepted — authenticated envelope, subject, and bounded evidence agree
unauthenticated-envelope: rejected — attestation envelope is unauthenticated
untrusted-signer: rejected — attestation signer is not trusted
signer-builder-mismatch: rejected — signer-builder pairing is not allowed
subject-substitution: rejected — subject digest does not match candidate bytes
missing-material: rejected — complete record omits required dependency: https://example.invalid/toolchains/python.tar.gz
unapproved-builder: rejected — builder is outside the approved trust boundary
incomplete-evidence: unknown — producer marks required evidence incomplete
incomplete-and-missing: unknown — producer marks required evidence incomplete; missing https://example.invalid/toolchains/python.tar.gz
The two unknown lines are not softer successes. They show that the statement
cannot support the required decision without inventing evidence. The rejected
cases contradict an independent trust or completeness assertion.
Decide: A provenance statement names the correct artifact digest, source commit, build type, and trusted builder. One dependency was downloaded by a repository-time script that the evidence collector cannot observe. May the system claim complete materials?
Reveal
No. The known fields can still be useful and authentic, but the materials dimension is incomplete. The verifier should preserve that limitation and apply its policy accordingly. Copying the declared top-level dependency into the record would identify an intention, not prove which bytes the script consumed.
Build provenance is a bounded, verifiable claim about an immutable artifact—not a graph dump and not a synonym for a successful build. Join the subject digest to authenticated source and resolved materials, the requested invocation and configuration, a documented builder trust boundary, and reachable Bazel/CI evidence.
Keep declarations separate from resolved evidence, the builder separate from the signer and executor, and known facts separate from omissions. Then verify the artifact digest, envelope, signer-builder pair, source, build type, and external parameters from the consumer side. Provenance can establish only what its measured evidence and trusted producer justify; it does not by itself prove physical location, policy compliance, complete artifact composition, or an uncompromised build platform.
Check your understanding · 4 questions
1.A producer upgrades its documentation and implementation to SLSA v1.2. What should a verifier expect for a Build Provenance v1 statement?
Select one answer
2.Which artifacts belong in resolvedDependencies when the trusted builder can observe them? Select all that apply.
Select all that apply
3.Match each verifier observation to the correct decision:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
4.Classify the boundary between provenance claims and verification:
Choose True or False for each sentence
Footnotes
-
SLSA v1.2 Build Provenance — current predicate URI,
buildDefinition,runDetails,resolvedDependencies, and best-effort completeness ↩1 ↩2 ↩3 -
Software Engineering at Google — Ch.21: Dependency Management — transitive dependency growth, forced security upgrades, and ownership risks in long-lived dependency networks ↩
-
{Fast, Correct, Secure} — Choose Three — CAS digest verification, the action cache's missing producer proof, and the resulting remote-build trust gap ↩