skip to content

How do you chain provenance across three repositories so a consumer can verify more than the last build?

level: seniorimportance: should knowfreq 52%

answer

  1. three builds, one check
  2. faithful record, not a judgment
  3. each stage records what it consumed
  4. digest of the input, never its name
  5. walk the hops; name the terminating gap

basics

~20 s

Every stage attests its own output and records, by digest, the inputs it consumed. A verifier then walks from the artifact in hand back through those recorded digests, checking each hop against expectations. Inputs recorded by name only are where the chain silently ends.

solid answer

~60 s

Verifying the final artifact proves only that the *last* build happened as claimed — it says nothing about how its inputs were produced, and a faithful record of a build that faithfully consumed a poisoned input still verifies green. To get a chain, each stage has to do two things: attest its own output with that output's digest as the subject, and record the digest of every input it actually consumed inside its own statement. Then a consumer can start from the bytes in hand, read the recorded input digests, look up *those* artifacts' statements, and check builder identity and source repository at each hop against expectations set per stage. Two rules make it work in practice: record the digest you consumed, not the name you asked for, and treat a hop with no statement as an explicit, inventoried gap rather than as silence. Build-level ratings do not transit either — a final artifact built to a high bar from inputs built to none is not a verified chain end to end.

go deeper

for a junior

Know that verifying one artifact's provenance covers only the build that produced it, and that the builds behind its inputs need their own statements to be checked at all.

for a middle

Explain how the link is formed: each stage attests its own output by digest and records the digests of the inputs it consumed, so a verifier can hop from one statement to the next.

for a senior

Demonstrate the walk with per-stage expectations and separate signing identities, and show that a compromised upstream build still yields green downstream checks. Insist that inputs be recorded by digest and that gaps be enumerated.

for a principal

Own the sequencing across teams that do not report to you: which hop to close first, how to make unattested inputs a visible estate-wide number, and how to avoid selling one strong hop as an end-to-end guarantee.

## The shape of the problem A realistic estate looks like this: a platform team builds a hardened base layer in its own repository; a product team builds an application artifact on top of it in a second repository; a release team assembles the deployable package in a third. Three builds, three teams, three sets of credentials, three statements — and one consumer, who almost always verifies the last one. That consumer learns something real: the deployable package was produced by the expected builder, from the expected repository, at a known revision. What they learn about the first two builds is **nothing**. And that is the gap an attacker uses. Compromise the platform build — the CI system with the broadest reach and usually the least scrutiny — and inject a backdoor into the base layer. The product build then honestly consumes it. The release build honestly consumes that. Every statement in the chain is accurate, every signature valid, every check green, and the backdoor rides into the availability of every service downstream of that base layer. The general principle worth stating out loud in an interview: **provenance is a faithful record of what happened, not a judgment that what happened was good.** Verifying one link tells you that link is faithfully recorded. It does not vouch for the links behind it. ## Building an actual chain A chain exists when two things hold at every stage: 1. **The stage attests its own output.** The subject of the statement is the digest of the bytes that stage produced. 2. **The stage records its inputs by digest.** A build provenance predicate has a place for this — the resolved dependencies of the build definition. What matters is that the digest of the base layer the product build *actually consumed* appears in the product build's own statement. With both in place, verification becomes a walk: - Hash the deployable package; find its statement; check signature, subject match, builder identity, source repository, entry point. - Read the input digests that statement records. One of them is the application artifact. - Fetch *that* digest's statement; check it against the expectations for **that** stage — a different builder, a different repository, quite possibly a different signing identity. - Repeat until you reach an input with no statement. Each hop carries its own expectations. "Expected builder" is not one value for the estate; the platform build and the release build should not be interchangeable identities, or a compromise of one lets it forge the other's outputs. ## The failure that makes chains fake The single most common defect is **recording the name you asked for instead of the digest you got**. A build that records "I used the base layer known as *platform-base:2026.08*" has recorded a mutable label, and there is no way to tie that label to a specific upstream statement. A build that records the digest it actually resolved has produced a link that a verifier can follow to exactly one statement. Content addressing is what makes a chain a chain; a name turns the chain into a suggestion. The second defect is stages that consume artifacts through paths nobody models: a script that curls a helper binary mid-build, a vendored blob checked into the product repository, a cached layer nobody attributes. Those inputs are real and unrecorded, so the chain has holes the walk cannot see. ## Where chains legitimately end, and what to do about it Every chain terminates somewhere — at the ecosystem package your build pulled, at a vendor artifact, at the base operating system. Termination is fine; **silent** termination is not. Make the endpoint a first-class fact: when the walk reaches an input with no statement, the verification result should say so, and the estate should be able to answer "which of our running artifacts depend on an unattested input, and what is that input". A chain gap you can enumerate is a risk decision; a chain gap you cannot see is an assumption that will be wrong at the worst moment. ## Ratings do not transit A related mistake to avoid: build-integrity levels describe **a particular build of a particular artifact**. They are not inherited by consumers of that artifact and they say nothing about the trustworthiness of the dependencies that build consumed — the build track deliberately makes claims about the build process only. So "our deployable is built at the top of the build track" is a claim about one hop. If the platform base layer was produced by a hand-run job on a laptop, the chain's real strength is that hop, not the best one in it. ## What I would actually implement first Start at the end and walk backwards one hop. Make the release build record the digest of the application artifact it consumed and verify the application artifact's statement before assembling. That single hop closes the most valuable gap for the least coordination, and it makes the next hop's absence visible as a concrete missing statement rather than as an abstract worry.

  • The platform build is compromised and injects a backdoor into the base layer. Do the downstream verifications fail?
    No — and that is the point. The product and release builds honestly consumed a poisoned input and honestly recorded doing so, so both statements are accurate and both verify. Only checking the platform hop's own statement, against expectations for that stage, would surface anything, and even then provenance shows where the artifact came from rather than whether its contents are safe.
  • A build records that it used a base layer by tag rather than by digest. What is lost?
    The link. A tag is a mutable label, so the recorded input cannot be tied to one specific upstream statement — several different artifacts may have worn that label. A verifier can neither confirm which upstream build produced what was consumed nor detect that it changed. Record the digest actually resolved, and the hop becomes followable.
  • How should a verifier report reaching an input that has no statement at all?
    Explicitly, as a named gap in the result, not as a pass. The output should identify which input terminated the walk so the estate can be queried for artifacts depending on unattested inputs. Silent termination is the dangerous outcome: it looks identical to a fully verified chain.
  • Should all three stages share one signing identity to simplify the walk?
    No. Separate identities per stage are what let expectations differ per hop and what stops a compromise of one build system from forging another stage's outputs. Convenience here trades away the isolation that makes multi-hop verification worth doing at all.

A signed delivery receipt proves the last courier handed over the box you are holding. It says nothing about who packed it, unless each courier recorded the sealed box's exact identity when they took it on.

saying these in an interview costs you the question

  • Assumes verifying the final artifact covers everything upstream
  • Records inputs by mutable name instead of digest
  • Uses one signing identity and one expectation set for every stage
  • Treats a missing upstream statement as a pass
  • Claims a build rating on the last hop describes the whole chain

context