Why does SLSA Build L2 reject a provenance file that your own release script wrote and signed?
answer
- The rung turns on the attestor
- Hosted platform generates and signs
- Ask who holds the signing identity
- builder.id is payload, not proof
- A signature by the suspect proves nothing
basics
~20 sSLSA Build L2 is about who attests, not what the document says: the hosted build platform must generate and sign the provenance itself. Provenance composed and signed by the build's own steps stays at Build L1.
solid answer
~50 sThe line between L1 and L2 is drawn at the attestor, not at the contents. L2 requires the build to run on a hosted build platform, and requires that platform to generate the provenance and sign it. If a step inside the build composes the JSON and signs it with a key handed to the job, then the thing being described and the thing describing it are the same principal — a compromised release step writes whatever it likes, and the signature only proves that step said it. A schema-perfect in-toto statement with a fully populated `buildDefinition` is still L1 if the build produced it. Note what L2 does and does not buy: a consumer can now detect tampering after the build, because the signature is the platform's. It does not stop a build that can reach the signing material from forging — that is what the rung above hardens.
code
json · 16 lines{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{ "name": "acme-cli", "digest": { "sha256": "9f2c...e1" } }
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://example.com/release-script/v1",
"externalParameters": { "ref": "refs/tags/v2.4.0" }
},
"runDetails": {
"builder": { "id": "https://example.com/ci/release-runner" }
}
}
}go deeper
Remember the one-line rule: at this rung the build platform generates and signs the provenance, not a step inside your build. Who signs matters more than what the file contains.
Explain the principal argument — a statement about a build authored by that build adds nothing — and describe verification order: check the envelope signature to learn who is speaking, then read the payload.
Diagnose a real pipeline in three questions: where does the build run, who composes the provenance, and can a build step read the signing key. Be clear about the one property L2 adds and the one it does not.
Frame the rung as an ownership boundary: attestation is a platform responsibility, not a per-team pipeline chore, and pushing it into the platform is what makes the claim uniform across every team that builds there.
## The question L2 actually asks Every rung of SLSA's build track answers "how much should a consumer believe this provenance?" L1 answers "it exists". L2 answers "**an entity other than the build steps produced and signed it**". That is the whole distinction, and it is why teams that pour effort into making their provenance document richer stay exactly where they were. Build L2 requires two things beyond L1: - The build runs on a **hosted build platform** — a build service, rather than an individual's workstation. "Hosted" is about being a service with its own identity; running your own build service in your own data centre qualifies. - That platform **generates the provenance and signs it**, and the signed provenance is distributed to consumers. ## Why self-written provenance fails Consider a CLI published to a package registry, whose release job builds the binary and then pipes a hand-written provenance document into the publish step, signing it with a key pulled from the job's secret store. Every field is populated. The signature verifies. It is still L1. The reason is a principal argument, not a formatting one. The provenance is a statement *about* the build. If the statement is authored by the build, then anyone who can influence the build — a contributor whose change lands in the release path, a compromised third-party step, someone who can edit the release script — can influence the statement to match. The signature does not fix this: it authenticates the release job, and the release job is the thing under suspicion. A consumer who verifies it learns "the release job said this about itself", which is what they already had. At L2 the signature is made by the platform, using an identity the job does not speak with. Now the statement has a second party in it. The consumer's check becomes meaningful: does this envelope carry a signature from the builder identity I expect, and does the payload's description of the source and entry point match my policy? ## `builder.id` is a claim, not a proof The field that trips people up is the builder identity inside the provenance predicate. It is just a string in the payload. Anyone writing a provenance document can put any builder identity in it, including the identity of a well-known build service they have never used. The field becomes trustworthy only because the **envelope is signed separately from the payload**, and the consumer checks that signature against a key or identity it already associates with that builder. Verification is: check the signature first, establish who is speaking, and only then read what they said. This is also why "we self-sign, but with a good key" is not an argument. The question a verifier asks is not "is this signed" but "is the signer someone whose word about this build is worth anything, and are they distinct from the build". ## What L2 buys, precisely - **Tamper-evidence after the build.** Once the platform signs, an artifact-plus-provenance pair modified in the registry, in a mirror or in transit fails verification. This is the concrete new property at this rung. - **A verifiable attestor identity.** Consumers can write a policy that names the builder they accept, instead of accepting any document that parses. And what it does not buy: L2 does not require the platform to prevent a build from reaching the signing material or from interfering with other builds. A sufficiently capable adversary running code inside the build may still be able to forge. Closing that is the job of the rung above, and it is a property of the platform rather than of your pipeline. ## Diagnosing your own position Three questions settle L1 versus L2 fast, and none of them involve opening the JSON: 1. Does the build run on a build service, or on somebody's machine? 2. Who composes the provenance — a step in the build definition, or the platform around it? 3. Whose key signs it, and can a build step read that key? If the answers are "a service", "the platform", and "the platform's, unreadable by the job", you are at L2 or better. If a step in the build definition writes the document or holds the signing key, you are at L1 regardless of how complete the document is. ## The failure mode this prevents The attack shape L2 addresses is not an outsider. It is the artifact and its description travelling together after the build, both under the control of whoever handled them last. Splitting the attestor from the built thing is the smallest change that makes the description worth reading — and it is usually a configuration change on the platform rather than a rewrite of the pipeline.
- The team offers to sign provenance with a key stored in the platform's secret store. Does that reach L2?No. A secret the job can read is a secret the build steps are signing with, so the build is still attesting to itself. L2 wants the platform to hold the identity and produce the signature outside the user-defined steps. Making that key unreachable from build steps is also part of what the next rung hardens, so the two changes tend to arrive together.
- A consumer verifies the signature on a provenance statement. What have they established?That a particular signer vouched for this exact payload, and that neither has been altered since. That is all. They then have to read the payload and decide whether the builder identity, source repository and entry point it names are ones their policy accepts. Signature verification establishes who is speaking, never that what they said is acceptable.
- Does moving to L2 mean buying a build service from a vendor?No. "Hosted build platform" means the build runs as a service with its own identity rather than on an individual's workstation. A build system you operate yourself qualifies, provided the platform — not the build definition — generates and signs the provenance.
A parcel with a courier's scan record beats a parcel with a note from the sender saying when they handed it over — even a beautifully written, signed note.
saying these in an interview costs you the question
- Thinks a complete, schema-valid provenance document implies L2
- Treats any valid signature as satisfying the rung
- Trusts builder.id because it is present in the payload
- Says L2 requires a commercial or cloud-hosted build service
- Claims L2 prevents forgery by code running inside the build