DevOps · Build Pipelines
Artifact Provenance from Commit to Deployed Digest
Signed attestations bind an artifact digest to the materials and builder that produced it. What the statement must contain, how it is verified, and which attacks it does not stop.
A deployment that pins an artifact by digest has established which bytes are running and nothing more. Those bytes could have been produced from any source, by any builder, with any modifications applied between the build and the push. The digest is a name; provenance is the record of how the named bytes came to exist. The two are separate artefacts, and a pipeline that has one without the other cannot answer the question that release reviews actually ask.
What the statement has to bind together
A useful provenance record binds four things with one signature, so that none of them can be substituted independently.
The subject: a list of artifact names paired with digests, in the same naming convention the rest of the pipeline uses. The digests are what the statement is about; the names are for humans and must not be the basis for any decision, since a name can be reused.
The materials: the digests of everything consumed by the build, at the granularity the build system can honestly report. A source revision digest, a lockfile digest, the digest of each dependency the builder fetched, the digest of the base image. The list is the answer to “what code is in this artifact” and it is where most of the practical value sits.
The builder identity: which system performed the build, identified by a name the verifier can match against a policy. This is the field that carries the security weight, because the verifier’s decision is usually “trust artifacts built by this builder for this repository, and nothing else”.
The invocation parameters: the external inputs the builder was given, such as the source repository, the reference, the build target, and the release flags. These are the values that distinguish a release build from a test build of the same commit.
The in-toto statement format is the common container for this data, and SLSA’s provenance predicate defines the field structure inside it. A minimal statement for an artifact built from a commit:
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{ "name": "app-linux-amd64", "digest": { "sha256": "9f2c...e104" } }
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://example.internal/buildtypes/release@v1",
"externalParameters": { "repository": "https://example.internal/app", "ref": "refs/tags/v2.4.0" },
"resolvedDependencies": [
{ "uri": "git+https://example.internal/app", "digest": { "gitCommit": "0b1d...c7" } }
]
},
"runDetails": {
"builder": { "id": "https://example.internal/builder/release@v3" },
"metadata": { "invocationId": "01J8..." }
}
}
}
The statement is unsigned data, and an unsigned statement is worth exactly as much as the file it sits in. Binding it to the builder requires a signature over the statement bytes, typically wrapped in an envelope that records the payload type and the signature material, so that a verifier holding the policy can check the signature against the expected identity and then read the fields with confidence.
The signature and the identity behind it
Two key management models are in use, and they fail differently.
Long-lived signing keys are held by the builder and rotated on a schedule. Verification requires distributing the public keys, which is a real problem in itself: a verifier needs the key for the identity it expects, and a key distribution mechanism that an attacker can influence undermines the whole chain. Key rotation without breaking verification requires accepting multiple keys per identity for a window, and revocation requires that verification consult current state rather than a cached list.
Ephemeral keys bound to a workload identity avoid distribution by making the signature verifiable against an external identity provider. The builder obtains a short-lived certificate that attests its workload identity, signs the statement with a key whose public half is in the certificate, and discards the key. The verifier checks the signature against the certificate, checks the certificate against the identity provider’s roots and against the expected identity, and checks the certificate’s validity window against the signing time. The signature is only meaningful while the certificate chain is verifiable, so the verification depends on the identity provider staying reachable or on the certificate chain being logged somewhere durable.
Both models benefit from a transparency log. A log is an append-only Merkle tree of entries, in the structure RFC 6962 defines for certificate transparency: each entry is a leaf, leaves are combined pairwise into parents, and the root hash commits to the whole log. An inclusion proof is the sibling hashes along the path from a leaf to the root, which is logarithmic in the number of entries, so a verifier can check that a signature’s log entry is in a log of tens of millions of entries with a few hundred bytes of proof. The log does not make a bad signature good. It makes a signature that existed at a time publicly discoverable, which is what allows a verifier to distinguish “the builder signed this” from “the builder signed this and nobody noticed”.
Verification is a policy, not a check
Checking a signature is a precondition. The decision a deployment gate makes is written as a policy over the attestation’s fields, and the policy is where the meaningful choices live.
The builder identity is the primary constraint. A policy that accepts any well-formed attestation is not a security control. The policy names the builder identifiers permitted to produce artifacts for a given artifact name, usually by matching a prefix that identifies the build service and a suffix that identifies the trusted configuration for that repository.
The source is the second constraint. The materials list must contain a revision of the expected repository, and for a release artifact the reference should be a tag rather than a branch. Checking that the commit is an ancestor of the repository’s main branch is a stronger statement, and it prevents the case where an attacker with push access to a branch has the builder produce an artifact from it.
The parameters are the third. A policy that requires the build to have run with the release configuration prevents an artifact built with debug flags or a development database endpoint from being accepted at deploy time, even though the builder and the source are correct.
Each of these checks needs the data to be present. A missing field must fail the policy rather than default to acceptable, because a builder that has been compromised or misconfigured can omit fields, and a policy that treats absence as “not applicable” is trivially bypassed by a builder that omits everything. This is the verification failure mode that shows up most often in practice, and it is a policy authoring bug rather than a cryptographic one.
What provenance does not establish
The record is only as strong as the builder’s isolation, and this is the boundary that matters most.
An attestation says that a builder with identity X produced bytes with digest D from materials M. If the builder’s execution environment can be influenced by someone other than the operator — by a pull request that modifies a build script the builder runs, by a dependency that executes at build time, by a cache poisoned through the mechanism described in Trust Boundaries in Shared Build Caches — then the attestation faithfully records a build that the attacker controlled. It is honest testimony about a compromised process. The SLSA levels are organized around this observation: signing a statement raises the bar, and hardening the builder’s isolation is what makes the signature mean something.
The materials list is also weaker than it looks. It records what the builder says it fetched. A build action that reads a network resource, a file outside its declared inputs, or an environment variable the tracking does not cover produces an artifact whose true materials include things the list omits. The list is a report from the same process being attested, which is why hermetic execution — no network, fixed environment, declared inputs only — is what makes the report trustworthy rather than merely signed.
Replay is a distinct concern. An attestation for an earlier artifact is a valid attestation, so a deployment that accepts any attestation for a given artifact name will accept one from a build that was superseded. The binding has to be to the digest being deployed, not to the name, which is the same principle that motivates Content-Addressed Artifact Identity. Freshness constraints on the attestation supply the other half: a policy that requires the attestation’s build to have occurred after the last release candidate was cut rejects the replay of an older build.
Finally, provenance says nothing about whether the artifact should be deployed. It establishes origin and process, not quality, not absence of vulnerabilities in the dependencies, and not that the source change was reviewed. Those are separate gates with separate evidence, and a provenance policy that is asked to enforce them will end up encoding review approvals into build parameters, which is a sign that a gate is missing elsewhere in the pipeline.
The pipeline’s obligation
Provenance is produced by the build and consumed at deploy, so the pipeline has to carry it. The practical requirements are small: the builder must be able to write the statement and sign it, the artifact store must keep the attestation alongside the artifact and address it by the same digest, and the deploy step must have the statement available when it resolves the reference. A pipeline that produces attestations and does not attach them to the artifact has made an audit trail that exists only in a log, which is not where the deploy gate looks.
The remaining question is what happens when the record is needed years later, after the builder has been replaced and its keys revoked. Answering it requires keeping enough state to verify a signature whose keys are no longer live — the certificate chain, the log inclusion proof, and the identity provider’s roots at the time — which is the same archival problem as rebuilding an artifact from an environment that no longer exists, taken up in Rebuilding a Pinned Artifact Without Its Original Environment.
References
- SLSA Specification v1.0 — OpenSSF
- in-toto Attestation Framework Specification — in-toto project
- RFC 6962: Certificate Transparency — IETF
- RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA) — IETF
- Sigstore documentation — Sigstore project