Continuum Academy · Chapter 04
Understanding SLSA Build Provenance
In the previous chapter, we opened an in-toto attestation. We can now read subject, predicateType and predicate. One question remains: what actually goes inside the Predicate when we want to describe how software was built?
Let’s return to payment-service-4.7.2.tar.gz. We will replace chapter 03’s three teaching fields with SLSA Build Provenance, following the approved SLSA v1.2 specification. Our goal is to read a structured account of a build: its inputs, platform, execution and output.
1 — SLSA does not replace in-toto
The Statement keeps _type, subject, predicateType and predicate. in-toto organizes the claim and its subject; SLSA defines the content of a build provenance claim here. This is a teaching distinction between their roles, not a new normative definition of an envelope. DSSE remains the separate layer covered in chapter 03.
The predicateType becomes exactly https://slsa.dev/provenance/v1. The type URI and the documentation URL are different. The page at https://slsa.dev/spec/v1.2/build-provenance explains the model; its address must not replace the URI in the JSON. The type’s v1 is therefore still correct when using the SLSA v1.2 specification.
2 — Our Statement becomes SLSA provenance
First, here is its outline. Empty objects mark the parts still to fill in: this excerpt does not yet describe a usable build. The digest is fictional and shortened.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{
"name": "payment-service-4.7.2.tar.gz",
"digest": { "sha256": "3a16...9f21" }
}],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {},
"runDetails": {}
}
}
subject represents what came out of the build. The Predicate’s two parts describe what defines the build and what concerns its execution. We add neither a top-level provenance field to the Statement nor a signature inside the Predicate.
3 — The two main halves
buildDefinition: which build was to be executed, with which inputs? This contains the build type, parameters and resolved dependencies.
runDetails: which platform executed it, and what do we know about this particular execution? This contains the builder, metadata and, optionally, additional artifacts.
Same Statement, a different Predicate
The subject remains payment-service-4.7.2.tar.gz. We replace chapter 03’s invented model with the SLSA Build Provenance structure.
On the left is chapter 03’s teaching Predicate. On the right is the SLSA type and its two parts. The subject and Statement schema are shared.
Shared structure · shortened digest
_type- https://in-toto.io/Statement/v1
subject- payment-service-4.7.2.tar.gz
sha256: 3a16…9f21
03 · Custom teaching Predicate
predicateType- https://example.com/attestation/build/v1
predicatesource · commit · builder
04 · SLSA Build Provenance
predicateType- https://slsa.dev/provenance/v1
predicatebuildDefinitionWhich build, with which inputs?
runDetailsWhich platform, which execution?
Changing the URI alone is not enough: the Predicate must follow the model it identifies.
The definition lets us understand the work to perform. The execution details distinguish one invocation from another. Two executions could share the same definition while having different identifiers and timestamps. This separation does not guarantee identical outputs: it organizes the information needed to examine the build.
4 — buildType: which kind of build?
buildDefinition.buildType is a URI identifying a build template: how to interpret its parameters, dependencies and associated instructions. It is not the CI system’s name, a shell command or the path to a YAML file.
For our example, we use https://example.com/buildtypes/payment-service/v1.
TEACHING EXAMPLE — NON-STANDARD URI. We define this fictional template as follows: take a repository and reference, resolve that reference, then execute release/build.yml from the resolved revision to produce the archive. The repository and ref fields belong to the interface chosen for this chapter.
In a real system, the build type definition needs to explain these rules unambiguously, including the meaning of external and internal parameters. A URI without an interpretable definition is not enough for the consumer.
5 — externalParameters: what the caller can influence
{
"externalParameters": {
"repository": "https://git.example.org/payments/payment-service",
"ref": "refs/heads/main"
}
}
This excerpt from buildDefinition describes the request sent to the platform: build from this repository and reference. repository and ref are not standard SLSA fields; their structure depends on our buildType. externalParameters describes the build’s external interface.
These values come from inputs the caller can influence. The SLSA model treats them as untrusted and requires consumers to verify them. A consumer might, for instance, accept only this repository and the intended release branch. A faithfully recorded value can still be forbidden by its policy.
6 — resolvedDependencies: what was actually resolved
The request contains refs/heads/main. This is a moving reference: a new commit can move the branch. For our invocation, it resolves to the commit used in the earlier chapters.
{
"resolvedDependencies": [{
"uri": "git+https://git.example.org/payments/payment-service@refs/heads/main",
"digest": {
"gitCommit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16"
}
}]
}
Request a branch, record a commit
The requested reference and the resolved revision have separate fields. This is a fictional example; the page does not perform a Git resolution.
The request keeps refs/heads/main. The dependency records the commit resolved for this execution, even if the branch moves later.
REQUESTED
buildDefinition.externalParameters
repository- https://git.example.org/payments/payment-service
refrefs/heads/main
This reference may point to a different commit tomorrow.
RESOLVED
buildDefinition.resolvedDependencies
uri- git+https://git.example.org/payments/payment-service@refs/heads/main
digest.gitCommit7d34a8b4e1f98c2040e9844ab754ae068943bb16
This value identifies the revision declared for this execution.
The commit is the same fictional value as in earlier chapters. These panels do not represent two output artifacts.
externalParameters preserves what was requested. resolvedDependencies records what was resolved, when known. Here, the URI connects the dependency to the request; digest.gitCommit identifies the declared revision. Looking up main today cannot replace the value recorded yesterday.
This field lists build dependencies; it is not reserved for Git. A downloaded compiler, source archive or other required components can also appear here. Our example keeps only the repository for readability. The list is not automatically exhaustive: a missing dependency must not be assumed not to exist.
7 — internalParameters: what the platform controls
buildDefinition.internalParameters contains parameters controlled by the entity represented by builder.id. Its schema also depends on the build type. These values can help with debugging, incident response or reproducing the build.
The distinction is who controls the input. A parameter chosen by the caller does not become internal simply because it is stored in this field. In the SLSA model, trust in internal parameters rests on trust in the platform. This field is optional; our complete example omits it.
8 — runDetails.builder: who executed the build?
{
"runDetails": {
"builder": {
"id": "https://example.com/builders/payments-release/v1"
}
}
}
This teaching URI identifies our build platform and its trust boundary. It does not necessarily identify a single machine, a developer or a key. A platform can include several machines and the services that orchestrate execution and record provenance.
BUILDER ≠ SIGNER
builder.id declares which platform performed the build. The signer authenticates the attestation using the chosen mechanism. One signer can cover several builders; that does not authorize it to claim any platform. A consumer must accept an appropriate signer–builder pair, not just two names found in the document.
9 — metadata: this particular execution
runDetails.metadata can contain invocationId, startedOn and finishedOn. The first identifies a particular invocation; the other two record when it started and finished. Two builds launched from the same commit can therefore have different metadata.
This information helps locate logs, correlate events or understand a duration. Its presence does not independently certify the time: these are timestamps declared by the platform. Signing the document does not turn them into independent timestamp evidence. Their guarantees depend on the system that produced them and the trust model.
10 — byproducts: additional build artifacts
runDetails.byproducts can reference additional artifacts useful for debugging or investigation: a log or evaluated configuration, for example. SLSA does not prescribe this list of examples or require every intermediate file to be retained.
The archive intended for the consumer stays in subject, alongside any other main outputs. Putting that archive only in byproducts would change the document’s meaning. We omit this optional field from the following JSON.
11 — The complete build record
The Statement now brings together the fields we have read. The structure follows SLSA Build Provenance; the build type, platform and values are fictional. The output digest has 64 hexadecimal characters, with no real archive attached. This document is neither evidence of an actual execution nor a signed attestation ready to verify.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "payment-service-4.7.2.tar.gz",
"digest": {
"sha256": "3a16000000000000000000000000000000000000000000000000000000009f21"
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://example.com/buildtypes/payment-service/v1",
"externalParameters": {
"repository": "https://git.example.org/payments/payment-service",
"ref": "refs/heads/main"
},
"resolvedDependencies": [
{
"uri": "git+https://git.example.org/payments/payment-service@refs/heads/main",
"digest": {
"gitCommit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16"
}
}
]
},
"runDetails": {
"builder": {
"id": "https://example.com/builders/payments-release/v1"
},
"metadata": {
"invocationId": "https://example.com/builds/payment-service/2026-09-18-001",
"startedOn": "2026-09-18T08:15:00Z",
"finishedOn": "2026-09-18T08:17:00Z"
}
}
}
}
Read subject first, then predicate.buildDefinition and predicate.runDetails. The requested repository, resolved commit and builder each have their own place. The invocation identifier and timestamps describe this execution without changing the build template.
12 — Reading from left to right
The diagram connects the JSON values rather than presenting them as a list of properties. The request supplies parameters; resolution supplies dependencies. The build type explains how to use them. The platform executes the build, and the archive becomes its output subject.
From inputs to the delivered archive
Follow a single invocation. Parameters, the build type and dependencies belong to buildDefinition; the platform and metadata belong to runDetails.
The inputs and template define the build executed by the platform. Resolved dependencies feed that execution, and subject identifies its output.
predicate.buildDefinition
01 · Request
externalParametershttps://git.example.org/payments/payment-service
refs/heads/main02 · Resolved dependencies
resolvedDependenciesgitCommit
7d34a8b4e1f98c2040e9844ab754ae068943bb1603 · Build template
buildTypehttps://example.com/buildtypes/payment-service/v1
04 · Platform · Execution
runDetails.builder.idhttps://example.com/builders/payments-release/v1
05 · Main output
subjectpayment-service-4.7.2.tar.gz
sha256: 3a16…9f21A reading of the declared relationships, not proof that the execution took place. Teaching buildType and builder URIs; fictional, shortened output digest.
Resolved dependencies are build inputs, including when they are obtained during execution. The diagram does not place them after the output. Metadata belongs to the invocation; it does not add another manufacturing step. This reading simplifies the declared relationships without imposing a universal timeline on platforms.
13 — What a consumer can verify
After the envelope checks from chapter 03, the recipient compares the provenance with its expectations:
| Consumer expectation | Value to examine |
|---|---|
| The received file is the declared subject | Computed file digest ↔ subject.digest |
| The request uses an authorized repository and reference | buildDefinition.externalParameters |
| The sources and dependencies are the expected ones | buildDefinition.resolvedDependencies |
| The build template is supported and accepted | buildDefinition.buildType |
| The platform is authorized for this signer | runDetails.builder.id and the verified signing identity |
SLSA provides information and requirements; it does not automatically perform these comparisons. The consumer’s tool and policy must implement them. Useful provenance must also match what the consumer expects, beyond being valid JSON. The Verifying artifacts guide develops this approach.
14 — Provenance can be valid and unacceptable
Consider a delivery separate from our fictional JSON: its provenance is correctly structured, its signature is valid, and the archive’s digest matches the subject. However, the consumer expects commit 7d34a8b…. The provenance declares a different commit.
The document may faithfully describe a build of the wrong code. The consumer should reject it under this policy, without claiming the signature is broken. The same reasoning applies to a forbidden branch or a platform that is not authorized for the verified signer.
CRYPTOGRAPHICALLY VALID ≠ POLICY ACCEPTABLE
This distinction makes a precise result possible: “valid signature, matching artifact, unauthorized revision.” It also identifies what to examine when explaining the rejection.
15 — What SLSA Build Provenance does not prove
This provenance does not automatically guarantee the absence of vulnerabilities or malicious code, secure dependencies or regulatory compliance. It does not make a compromised platform honest or create trust in an unknown builder.
Assurance depends on the trust model, the platform and the SLSA properties actually satisfied. A more detailed account provides more values to check; by itself, it does not make the system that recorded them more reliable.
16 — SLSA Provenance ≠ SLSA Level
FORMAT ≠ ASSURANCE LEVEL
Producing a document in the SLSA Build Provenance format does not automatically confer a SLSA level. The format organizes information; levels concern the requirements met by the build and by the production of that information.
We therefore need to examine the platform and process properties, not look for a magic label in our JSON. The Build Track levels are a separate subject that we will not cover in detail here.
17 — Build provenance and Source provenance
SLSA v1.2 uses provenance as a general concept and distinguishes Build provenance from Source provenance, among others. This chapter covers only Build Provenance: connecting build outputs to the process and inputs that produced them.
Mentioning a commit in resolvedDependencies does not turn our document into Source provenance. We are identifying a build input here, without describing how that source revision was created and managed.
18 — Where does Continuum Attest fit?
The CLI stores a native receipt, then converts it through attest export --format in-toto into a Statement with a SLSA Predicate and DSSE envelope. The current code applies this specific mapping:
buildTypeishttps://alien6.com/attest/pipeline/v1;externalParameters.pipelineHashreceives the pipeline hash.subjecttakes each step’s name andoutput_hash, underdigest.blake3;resolvedDependenciestakes itsinput_hashin the same way.builder.idbecomeshttps://alien6.com/attest@vfollowed by the CLI version.invocationIdderives from the hash of the receipt’s canonical content; completion comes from its timestamp and the start is calculated by subtracting its duration.
These are hashes of the declared sets for each step: this mapping does not automatically provide our repository/reference pair and Git commit. attest import --format in-toto checks the exported profile, its consistency and its signature against the local trust store; it neither recomputes the delivered artifact nor applies our expected-revision policy. The CLI reference documents these commands.
19 — Takeaways
subject → what was produced
buildType → the build template
externalParameters → the external interface and its values
resolvedDependencies → dependencies actually resolved
builder.id → the declared platform
runDetails → this particular execution
We can now read build provenance. The next step is to produce and verify evidence ourselves: 05 — Produce your first evidence with Continuum Attest, coming soon.