Why does a provenance attestation stop verifying when a supplier rebuilds the same source revision?
answer
- what exactly is the statement about?
- bytes versus source revision
- the subject field is a hash
- same revision, patched base, new output
- old statement now describes a different artifact
basics
~20 sA provenance statement binds to a digest of the artifact's bytes, not to the source revision. A rebuild that picks up a patched base produces different bytes and a new digest, so statements issued for the old build no longer match the artifact in hand.
solid answer
~50 sThe statement's `subject` names the artifact by cryptographic digest, so the binding is to **bytes**, not to a source revision. When a supplier rebuilds the same tagged revision to pick up an operating-system patch, the inputs changed even though the source did not: new bytes, new digest, and every previously distributed statement now describes an artifact nobody has any more. Your gate failing on that honest release is the system working — a subject mismatch means the statement is about something else, and it should fail closed. The wrong fix is to relax the check to "same source repository and revision", because an attacker who can influence the build produces exactly that: matching source coordinates, different bytes. The right fix is operational: fetch the artifact you actually resolved *together with* its current statement at verification time, and treat the digest change as a change that goes through your normal approval, rather than as a reason to downgrade the check.
code
json · 15 lines{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{ "name": "data-connector",
"digest": { "sha256": "9f2c...a41b" } }
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"externalParameters": { "ref": "refs/tags/v2.4.1" },
"resolvedDependencies": [ "..." ]
},
"runDetails": { "builder": { "id": "..." } }
}
}go deeper
Remember that the statement names its artifact by hash. If the bytes change for any reason, that hash changes and the old statement no longer applies.
Explain the subject-digest anchor and why identical source does not imply identical output once a base or toolchain input moves. Say why relaxing the match to source coordinates destroys the property.
Show the operational answer: resolve the artifact and its current attestation together, express expectations as claims rather than frozen digests, and route the new digest through normal change approval instead of disabling the gate.
Own the failure mode where an honest supplier rebuild stalls releases and someone turns the check off permanently. Design the path that absorbs legitimate re-releases so enforcement survives contact with real vendors.
## What the binding actually is An in-toto style statement has four parts: a `_type`, a `subject` list, a `predicateType`, and a `predicate`. The subject entries carry a name and a **digest map** — the cryptographic hash of the artifact the statement is about. The predicate (for build provenance, a SLSA provenance predicate) holds the interesting claims: which builder ran, from which source repository and revision, through which entry point, and which dependencies it resolved. The whole thing is placed in a signed envelope, where the signature covers the payload — the statement itself is not self-signing, and a statement lifted out of its envelope carries no assurance. The crucial property for this question is which of those parts the verifier keys on. **The subject digest is the anchor.** Verification means: hash the bytes you hold, find them in the subject list, then evaluate the predicate's claims. Everything else in the statement is a claim *about* those specific bytes. ## Same source, different bytes Source revision and artifact bytes are not the same identity, and conflating them is the mistake this question probes. A supplier who rebuilds the identical tagged revision to absorb an operating-system security patch has changed the build's *inputs*: a patched library is now baked in. Output bytes differ, so the digest differs. Even where a build has been engineered so that repeating it with identical inputs yields identical bytes, that guarantee is conditioned on the inputs — change the base and the output legitimately changes. So the moment the supplier republishes, the statements you already hold describe an artifact you no longer receive. Your gate refuses the new one. Nothing is broken, nobody is malicious, and the check is behaving correctly: a subject mismatch does not mean "probably fine, slightly newer" — it means *this statement is about a different artifact*. ## Why not just relax to source coordinates The tempting repair is to stop matching on digest and match on "builder X, repository Y, revision v2.4.1" instead. That deletes the property you were buying. Verifying claims without binding them to the bytes in hand means any artifact accompanied by any statement with the right coordinates passes — including one produced by a compromised build system from the same revision, which is the single scenario provenance exists to catch. Digest binding is what makes the claims non-transferable. It is also worth being precise about who is at fault here. Nobody is. This is an **honest vendor with a broken binding**, and the asset at risk is availability and change-control truth: either your deploys stall on a legitimate patch, or somebody disables the check to unblock a release and it never comes back on. That second outcome is how verification programmes die. ## The operational fix Stop treating an attestation as a durable document you captured once. Treat it as something you resolve *with* the artifact: - At verification time, fetch the statement that accompanies the artifact you actually resolved, rather than a copy captured weeks ago in a ticket or a spreadsheet. - Keep expectations expressed as **claims** (this builder identity, this source repository, this entry point), not as a frozen allowlist of digests. Claims survive a rebuild; a digest allowlist does not. - Where you do pin exact digests for reproducibility — a good practice for your own deployments — accept that a supplier rebuild is a *change*, and route it through the same approval that any other version bump would get. The gate failing is the notification that a change happened. - Have somewhere for the supplier's re-release to land other than "engineer disables the check". If your only two options are pass and disable, you will get disable. ## The related trap: republishing without rebuilding The mirror image occurs when release automation publishes a *previously built* artifact under a new version instead of rebuilding it. Two outcomes, both bad. If the bytes are byte-identical, an old statement still verifies by digest, but its claims describe a different release than the one consumers are resolving — the metadata is stale and the statement silently vouches for the wrong thing. If the automation repackages and metadata inside the archive changes, the bytes change, no statement exists for the new digest at all, and consumers see an unattested release from a supplier they thought was covered. The rule that avoids both: **one build, one artifact, one statement produced for the exact bytes that get published**, generated by the thing that did the building. ## What a strong answer sounds like "The subject is a digest, so the statement binds to bytes. Rebuilding the same revision with a patched base changes the bytes, so old statements stop matching — correctly. I would not loosen the match to source coordinates, because that is exactly what a compromised builder can reproduce. I would fetch the artifact and its current attestation together at verification time and treat the new digest as a change to approve."
- Release automation republishes a previously built tarball under a new version instead of rebuilding — what breaks?Either the bytes are identical, so an old statement still verifies by digest while its claims describe a different release — the statement vouches for the wrong thing — or the automation repackages, the bytes change, and no statement exists for the digest consumers resolve. Both break the binding between what the ecosystem hands people for that version and what any statement actually describes.
- Should a subject digest mismatch fail closed or warn?Fail closed. A mismatch is not a degraded signal; it means the statement is about some other artifact, so you have no provenance at all for the bytes in hand. Resolve it by fetching the attestation that accompanies the artifact you actually pulled, and approving the new digest as a change — never by downgrading the check to a warning.
- Why not verify on builder plus source repository and revision, and skip the digest?Because a compromised build system produces artifacts with exactly those coordinates. The digest is what makes the claims non-transferable to other bytes; without it, any artifact carrying a statement with the right coordinates passes, which is precisely the attack provenance exists to catch.
saying these in an interview costs you the question
- Assumes the same source revision implies the same artifact
- Proposes matching on repository and tag instead of digest
- Calls a subject mismatch a warning and proceeds
- Thinks the signature or the statement expired
- Believes one statement covers all future rebuilds of that source