Skip to content

Build Provenance Explained

Lesson 5 of 10Advanced14 min readGit Security & DevSecOps · Supply Chain SecurityVerified: SLSA v1.2 provenance model; GitHub artifact attestations, September 2026

Provenance is the answer to one question, asked about a specific artifact:

Where did this come from, and how was it made?

The question sounds administrative. It is not, and the reason is that for most software the honest answer is a guess. A binary in a registry, a package on a mirror, a container image somebody deployed last quarter — the connection between those bytes and the source anybody reviewed is usually a convention rather than a fact.

Provenance is a record of how an artifact was produced, ideally signed by something the build cannot forge.

The core fields:

FieldRecords
SubjectThe artifact, by digest — what this record is about
Builder identityWhich system performed the build
Build typeWhat kind of build process ran
SourceWhich repository and commit
InvocationWhat triggered it, and with what parameters
Resolved inputsThe top-level inputs, including dependencies where known
MetadataWhen it started and finished

Read together, those fields let somebody who was not there reconstruct what happened — and, more usefully, detect when what happened is not what they expected.

Why “rebuild and compare” does not work

Section titled “Why “rebuild and compare” does not work”

The intuitive verification is to rebuild the artifact from the stated source and check the bytes match. This mostly fails, and understanding why explains the existence of provenance.

Two builds of the same commit typically differ:

  • Timestamps embedded in archives, binaries and metadata
  • Absolute paths from the build directory baked into debug information
  • File ordering determined by filesystem enumeration
  • Toolchain versions that differ between machines
  • Hash seeds randomised per process
  • Locale and timezone affecting string formatting and sorting

None of these are defects. They are ordinary properties of build tooling that was never asked to be deterministic, and eliminating them is the practice known as reproducible builds — real, valuable, and achieved by a minority of projects because it requires effort across an entire toolchain.

Provenance is the pragmatic alternative. Rather than proving the artifact can be independently reconstructed, it records verifiable facts about how it was produced, signed by something the build being described cannot influence.

The three artefacts most often conflated, each answering a different question about the same artifact.

QuestionEstablishesSilent about
SignatureWho vouches for these bytes?An identity produced or endorsed this, and it has not changedWhere it came from; what is in it; whether it is good
ProvenanceWhere and how was it built?Source, builder, process, inputsWhat is inside it; whether it is good
SBOMWhat is inside it?Components and versionsWhere it came from; whether any component is safe

A worked example. You have a container image and want to know whether to deploy it.

The signature tells you it was signed by an identity you recognise and has not been modified since. It does not tell you the identity built it from your source — an identity can sign anything.

The provenance tells you it was built by workflow X in repository Y from commit Z. That is the link back to code somebody reviewed, and it is the claim a deployment policy asserts on.

The SBOM tells you it contains a library at a version with a known critical vulnerability.

Three documents, three answers, and the deployment decision needs all three. In practice the signature and the provenance travel together — provenance is normally delivered as a signed attestation, which is why the two get merged in casual usage. They remain distinct claims: the signature makes the provenance trustworthy, and the provenance is what the signature is about.

Full reproducibility is a large project. Several of its components are individually cheap, help debugging as much as security, and make provenance more useful by narrowing what could differ between two builds of the same commit.

Fix timestamps. SOURCE_DATE_EPOCH is honoured by a growing set of build tools and archivers, and setting it from the commit date makes archive contents stable:

Terminal window
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
export SOURCE_DATE_EPOCH

Pin the toolchain. A compiler version in a workflow file rather than “whatever the runner image has this month”. The runner image changes; your build should not change with it silently.

Sort what enumerates. Archive and bundle creation that iterates a directory picks up filesystem ordering. Sorting explicitly removes a source of variation that also produces confusing diffs.

Fix locale and timezone. LC_ALL=C and TZ=UTC eliminate a category of formatting and sorting differences that are invisible until they are not.

Strip build paths where the toolchain supports it, so debug information does not embed the directory the build happened in.

None of this makes a build reproducible on its own. All of it makes builds more comparable, which means that when two builds of the same commit do differ, the difference is more likely to be interesting. That is a debugging benefit that happens to be a security benefit.

Provenance does not eliminate trust. It relocates it, and knowing where it lands is what stops provenance being over-read.

What you still have to trust

A vertical chain: the build platform's honesty, the platform's isolation, the signing infrastructure, the transparency log, and the verifier's policy. Provenance makes claims checkable but each of these remains trusted.

The build platformIts statements about runs are accuratePlatform isolationA build cannot forge another build's provenanceSigning infrastructureCertificates are issued to the right identitiesTransparency logEntries are append-only and completeThe verifier's policyIt asserts the right things and fails on mismatch

Every stage is trusted. What provenance changes is that the trust is now placed in specific, auditable systems rather than in an unstated assumption that the artifact in the registry is the right one.

That is a real improvement and it is not the same as trustlessness. The honest formulation: provenance moves you from “we assume this is the right binary” to “we trust GitHub’s statement that this binary came from this commit, and we can check that statement has not been altered.”

The last stage in the diagram is the one under your control and the one most often weak. A verifier that checks a signature exists, without asserting the repository, the workflow and the ref, is trusting the artifact to describe itself.

That the source is good. It names a commit. It has no opinion about the commit’s contents.

That the build did the right thing. A build script that compiles the wrong file produces accurate provenance about having done so.

That the artifact is free of vulnerabilities. Origin is not quality.

That the build environment was clean. Unless the platform provides isolation guarantees — which is what the higher SLSA build levels are about — a compromised build step may have influenced the output while the provenance remains accurate about which workflow ran.

That the inputs were what you expected. Provenance records resolved inputs to the extent the platform can determine them. A build that downloads a binary from a URL at run time may record the URL and not the bytes.

Each of these is a reason for a different control, not a criticism. Provenance is a narrow instrument that does one thing well.

Provenance that nobody evaluates is metadata. The evaluation is a policy, and a policy is a list of assertions plus an action on failure.

A minimal useful policy for a deployment:

  1. An attestation exists for the artifact’s digest.

  2. It was built from the expected repository. Named by you, not read from the artifact.

  3. By the expected workflow. The release workflow specifically, not any workflow in the repository.

  4. From an expected ref. A release tag, or the default branch — not an arbitrary branch, which would let anyone with push access produce a deployable artifact.

  5. The commit is an ancestor of the branch you believe you released from, if you want to close the gap where a valid build came from a legitimate but unintended commit.

  6. On failure, the deployment does not proceed.

Steps 2 to 4 are what turn verification from a formality into a control. Step 6 is what turns the control from documentation into a boundary.

Questions provenance answers in ordinary operations

Section titled “Questions provenance answers in ordinary operations”

The security case is the compromised artifact. It is not the case that gets provenance adopted, because it is rare. The operational cases are frequent, and they are what makes the record earn its place.

“Which commit is running in production?” Asked during every incident. Without provenance the answer is derived from deployment records and CI logs, both of which rotate. With it, the artifact answers for itself.

“Who owns this image?” A vulnerability is reported against a container in your registry. Nothing in the image says which team built it or from what. Provenance names the repository, and the repository has owners.

“Was this deployed from the release branch or from somebody’s feature branch?” More common than it should be, and unanswerable after the fact without a record of the ref.

“Is the artifact in staging the same one we are about to promote?” Comparing digests answers this; comparing tags does not, because tags move.

“When did this artifact enter the registry, and what was in the source then?” The archaeology question, asked months later, usually while somebody is waiting.

Each of these is a question a team asks anyway, answers slowly, and answers with uncertainty. That is a stronger argument for adoption than the compromise scenario, and it is the one to make internally — provenance pays for itself in incident response long before it catches an attack.

In the deployment workflow is the easy place, and it covers artifacts your pipeline deploys. It does not cover anything deployed another way.

At admission — a Kubernetes admission controller, a registry policy, a package manager configuration — covers everything arriving, including artifacts pushed by somebody bypassing your pipeline. This is the stronger position and the harder one to operate, because a failed admission during an incident is a conversation nobody wants to have at 03:00.

The practical progression is to start in the pipeline, get the policy right, and move it to admission once failures are rare and understood. Moving it before then produces an outage that gets the control disabled.

Provenance describes one artifact. That is only useful if one artifact is what travels through your environments, and a surprising number of pipelines rebuild instead.

The pattern that makes provenance meaningful end to end:

Build once, in the release workflow, producing an artifact identified by its digest.

Attest it, binding the provenance to that digest.

Promote by digest. Staging deploys image@sha256:abc…. Production deploys the same image@sha256:abc…. Not the same tag — the same digest.

Verify at each promotion, so the artifact entering production is confirmed to be the one that was built and tested.

The alternative — rebuilding for each environment — breaks the chain in a way that is easy to miss. Two builds of the same commit produce two different artifacts with two different digests and two separate provenance records. Everything you tested in staging applies to a binary that is not the one in production, and no amount of attestation fixes that, because both attestations are accurate.

This is also the argument for tags being labels rather than identifiers. A tag pointing at a digest is a convenience for humans. The deployment should reference the digest, so that moving the tag cannot change what runs.

The producer side gets the attention. The consumer side is where provenance changes your risk.

For each significant input — base images, third-party actions, key packages — ask:

Does it publish provenance? Increasingly, yes. npm packages published with provenance, container images with attestations, actions from repositories that attest their releases.

Do you verify it? Almost certainly not, and this is the gap worth closing.

What would you do on failure? If the answer is “log it”, you have not built a control.

A realistic starting point that is not a programme: verify the inputs to your release pipeline. It is the workflow where a compromised input reaches the most places, and it usually has few enough inputs to enumerate in an afternoon.

Threat. An artifact reaches production that was not built from the source that was reviewed, and nothing establishes the discrepancy.

Attack surface. The build platform, the build definition, the build’s inputs, artifact storage, and the deployment configuration that selects what runs. Also the provenance record itself, if anything other than the platform can write it.

Impact. A compromised artifact distributed through your own trusted channel — which is the highest impact available in this pillar, because it arrives by the route everything legitimate arrives by.

Control. Provenance generated by a platform the build cannot influence, and a deployment policy that refuses artifacts whose provenance does not match a locally-held expectation.

Verification. Two tests, and both are needed. Confirm that a legitimate artifact passes. Then construct one that should fail — built from a different workflow or a different ref — and confirm the policy rejects it.

The second test is what distinguishes a working control from a step that always passes. Policies fail open in mundane ways: a typo in a workflow path, a variable that is empty so the assertion matches nothing, a step whose failure is swallowed. None of those are visible until something is supposed to be rejected.

The concept generalises further than the tooling usually implies, and it is worth knowing because the same question applies to artifacts nobody thinks of as build outputs.

Infrastructure. A Terraform plan applied to production — which commit produced it, which run applied it, against which state? The question is identical and the answer is usually less well recorded than for application builds. See Deploy Terraform.

Configuration. A configuration bundle deployed to a fleet. Same question.

Machine learning models. Which training code, which dataset version, which hyperparameters, which run? Model provenance is the same structure with different fields, and it is the case where “we cannot reproduce it” is most often accepted as normal.

Documents and datasets. Anything generated by a pipeline and consumed downstream.

In each case the useful move is the same: record the origin at production time, sign it with an identity the producer cannot forge, and check it at the point of use. The tooling in this cluster is oriented at software artifacts, and Cosign will sign any blob — so where the standard actions do not fit, the pattern still does.

Expecting to verify by rebuilding. Builds are not deterministic by default, which is why provenance exists.

Treating provenance as a safety claim. It records origin.

Verifying existence rather than content. “An attestation exists” is nearly no assertion.

Deriving the expected identity from the artifact. Self-vouching.

Verifying in the pipeline only. Covers your path, not everything that arrives.

Provenance from an untrusted builder. A record signed by a build platform an attacker controls is an accurate record of a compromised build.

Recording provenance and retaining it for less time than the artifact lives. The question arrives after the evidence has gone.

Confusing provenance with an SBOM. Where it came from and what is inside it are different documents, and consumers usually need both.

Provenance is a chain of custody record for an artifact. It does not make the artifact good, and it does not remove the need to trust the builder. It makes the builder’s claim about the artifact checkable — which is the difference between believing the right binary is deployed and being able to tell.

  • Provenance records subject, builder, build type, source, invocation, resolved inputs and timing
  • Rebuilding and comparing usually fails, because builds are non-deterministic by default
  • Reproducible builds are the stronger property; provenance is the achievable one
  • Signature, provenance and SBOM answer who vouches, where from, and what is inside
  • Provenance relocates trust to specific auditable systems; it does not eliminate it
  • The weakest link is usually the verifier’s policy, not the cryptography
  • A policy must assert the repository, workflow and ref, and must fail the deployment on mismatch
  • The expectation has to come from your configuration, never from the artifact
  • Enforcing at admission covers everything arriving; enforcing in the pipeline covers your path
  • Verifying provenance on what you consume is the half that protects you rather than your consumers

Use a disposable repository that builds an artifact.

  1. Build the same commit twice, in two runs. Compare the artifacts byte for byte. Predict: are they identical?

  2. If they differ, find out why — diffoscope if available, or compare sizes and embedded strings. Predict: timestamps or paths?

  3. Generate provenance for one build. Read the attestation and identify the source commit, the workflow and the trigger.

  4. Verify it asserting the correct repository, then a different one. Compare the outputs.

  5. Build the same artifact from a branch rather than the default branch and generate provenance. Predict: does a policy that only checks the repository distinguish the two?

  6. Add a ref assertion and re-run step 5. Predict: does it now fail?

  7. Write down what your policy would have to assert to be meaningful for your real pipeline. That list is the deliverable.

  8. Delete the repository.

GitHub Actions Security ChecklistToken permissions, fork pull requests, script injection and supply chain — with the attack each item prevents.

The repository security templates — secrets management and least-privilege token guides — are in the Professional Toolkit.