Build Provenance: Proving Which Commit Is Running in Production

An image tag tells you what someone named a build; build provenance tells you what the build was made from.
Build provenance ties a running artifact to the commit that built it. Why image tags are not enough, where correlation breaks, and what to record at build time.
October 22, 2026

A CVE lands on a Monday. By noon you know which repositories declare the vulnerable package. By Tuesday most of them have a fix merged. What you still cannot answer is the only question the incident channel cares about: which running workloads were built from a commit that carries the fix, and which are still serving the old code?

That gap is a build provenance problem. Build provenance is the record that ties an artifact, such as a container image or a deployed assembly, to the source revision and build process that produced it. Without it, "fixed" means "merged to main," and merged to main is not a security outcome.

This post covers what build provenance is, why an image tag is not a substitute, the four ways a running artifact can be traced back to its commit, where each one breaks, and what to put in place so the question above takes seconds instead of a war room.

What build provenance is

SLSA defines provenance as an attestation that a build platform produced a set of artifacts from a given source, using a given build process. Three facts have to be present for the record to be useful: the source (a repository and a commit), the builder (what ran the build and how), and the output (an immutable identifier for the artifact, normally a digest).

Those three facts form a chain. Source to build, build to artifact, artifact to running workload. Break any link and you are back to guessing.

Most organizations have the first link, because CI knows the commit it checked out, and the last link, because the cluster knows the digest it pulled. The middle link, where the commit is written onto the artifact in a form that can be read later, is the one that is usually missing.

The SLSA Build levels grade how trustworthy that chain is. Build L1 asks only that provenance exists. L2 requires that a hosted build platform sign it. L3 requires the build to run on hardened infrastructure that prevents tampering with the provenance.

For the production question in this post, L1 is where the value starts. You need the record before it makes sense to worry about whether it was forged.

Why an image tag is not build provenance

Most teams answer "what is running" by reading the image tag off the deployment manifest. api:v2.3.1 looks like an answer. It is a name.

  • Tags are mutable. The same tag can be pushed twice with different contents. latest is the famous case, but a version tag re-pushed after a hotfix is just as common.
  • Tags carry no source. Nothing in v2.3.1 names a repository, a branch or a commit. The mapping lives in someone's head or in a release spreadsheet.
  • Digests are immutable but opaque. A sha256 digest identifies the bytes exactly and says nothing about where those bytes came from.

The practical consequence: a tag can tell you two clusters run the same thing. It cannot tell you whether that thing contains commit 8f3a1c2. For that you need the commit recorded on the artifact, or attested beside it.

Signed attestations and OCI labels: the strong links

There is no single right mechanism for recording the commit. The four below run from strongest to weakest, and most organizations use two or three at once because their fleet is not uniform.

1. Signed provenance attestations

The build platform emits a signed statement in the SLSA provenance format naming the artifact digest, the source repository and commit, and the build definition. GitHub artifact attestations do this for images and binaries built in GitHub Actions, and anyone holding the artifact can verify the statement against GitHub's signing identity. This is the strongest form because the builder signs it, not the Dockerfile author.

The cost: a build platform that supports it, a verification step on the path to production, and a place to look the attestation up when the image is later observed running.

2. OCI image labels

The OCI image specification reserves annotation keys for exactly this purpose. org.opencontainers.image.revision holds the source control revision, and org.opencontainers.image.source holds the URL of the source repository. A build that sets both has written its own provenance onto the artifact, readable by any registry or scanner that inspects the image config.

In a Dockerfile, pass the commit in as a build argument and write it as a label in the final stage:

ARG GIT_SHA

LABEL org.opencontainers.image.revision=$GIT_SHA org.opencontainers.image.source="https://github.com/example-org/api"

Then have CI supply the argument and tag the image with the same commit:

docker build --build-arg GIT_SHA=$(git rev-parse HEAD) -t registry.example.com/api:$(git rev-parse --short HEAD) .

One trap: Dockerfile labels are inherited from the base image unless the build overrides them. A service image built on an internal base that set org.opencontainers.image.revision will carry the base's commit, not its own, if the service Dockerfile never sets the label. Set it in the final stage, every time.

Commit tags and runtime metadata: the links you already have

3. The commit as the image tag

The simplest mechanism, and the most widely used: tag the image with the commit SHA, short or long. The tag is still mutable in principle, but in practice a SHA tag is pushed once, and the tag text is enough to find the commit in the repository's history. It needs no attestation infrastructure.

Its weakness is that it is a naming convention, not a record. A team that switches to version tags loses it, and a short SHA has to be resolved against the right repository's history.

4. Metadata the runtime exposes

Some platforms and frameworks record the source without any container label. A .NET assembly built from a git checkout can carry source revision information in its assembly metadata. A Spring Boot service carries its application name in application.properties or application.yml. A hosting platform such as Vercel reports the git origin and commit SHA of each deployment through its own API.

None of these is provenance in the SLSA sense, since nothing signs them, but they are often the only link available for workloads that were built before anyone thought about provenance.

Where correlation breaks

Every organization that tries to build this map finds the same failure modes. Knowing them in advance is most of the work.

  • No label, no tag, no attestation. The image was built by a script nobody has touched in three years. The only link to source is the image name, and the image name is api.
  • Compiled output with no file match. Correlation that matches files in the image against files in the repository works for interpreted languages and fails for Go, Java, .NET and Rust, where the image holds a binary or a jar and none of the source. Compiled workloads need the commit written on the artifact or reported by the runtime.
  • GitOps indirection. The manifest lives in a second repository. The commit that triggered the rollout is the config repository's commit, not the application's. Provenance has to point at the application repository or the chain ends at a YAML change.
  • Short SHAs across many repositories. Seven characters is plenty inside one repository. Across hundreds, a short SHA has to be resolved against the right history, so the correlation also has to know which repository to search.
  • Rebased and squashed history. A SHA recorded at build time can vanish from the branch if the branch is rewritten before merge. The provenance is then correct and unresolvable at the same time.
  • Manual links that rot. A spreadsheet mapping services to repositories is accurate on the day it is written. Teams rename, split and retire repositories without updating it.
  • Inherited base image labels. Covered above. The label is present, and it names the wrong repository.

What to do: a build provenance checklist

None of this requires a platform purchase. It requires a few conventions enforced in CI and a place to look them up.

  1. Set org.opencontainers.image.revision and org.opencontainers.image.source in the final stage of every Dockerfile, from CI-provided build arguments. Fail the build if the arguments are empty.
  2. Tag images with the commit SHA in addition to any version tag. Keep the version tag for humans and the SHA tag for machines.
  3. Where the build platform supports it, emit signed provenance attestations and verify them at deploy time. Start with the images that face the internet.
  4. Build release images from the merge commit on the main branch, not from the pull request head, so the recorded SHA survives the merge.
  5. For compiled services, make sure the runtime exposes something that names the source: assembly metadata, an application name, or a version endpoint that returns the commit.
  6. Record the deploy, not only the build. The link from image digest to running workload is a runtime observation, so whatever holds your provenance has to see the cluster, the ECS service or the function as well as the registry.
  7. Review the unmatched. Any correlation will have workloads it could not place. Treat that list as a backlog with an owner, the same as a findings queue.

The order matters. Items 1 and 2 are a one-line change per Dockerfile and give you most of the value. Attestations are the goal, not the prerequisite.

How Heeler correlates deployments to source

Heeler's Context Engine builds the artifact-to-commit chain from whatever evidence exists, instead of requiring one convention across the fleet. A container image is matched to its source repository by build provenance, by its image label, by service correlation, by the deployment manifest, by a name match offered as a suggestion, or by a link set by hand. The image detail page shows the matched repository with a badge naming how the match was made, and the base image with its own badge: provenance, annotation, label or inferred.

Compiled workloads correlate without a file match. An image whose tag or label is a git commit is matched to the repository whose history contains that commit, and short-SHA tags resolve. A .NET deployment is matched to its repository and commit through the build provenance its entry assembly records. A Java deployment is matched on spring.application.name, and GitLab subgroup repositories match on their repository URL.

Catalog > Services has a sortable Last Correlated column, so a service whose link to source has gone stale shows up as a row to fix, not as a surprise mid-incident.

Once a deployment is correlated, the chain pays off twice. Catalog > Deployments lists every running instance with its changeset, container tag, environment and internet accessibility, and exports a CycloneDX SBOM of exactly what is running in that instance. And a dependency finding moves through Active, Fixed and Deployed, with a per-deployment breakdown of which changesets are still Affected and which are Resolved.

The fix-and-verify use case walks that loop: from a finding, through a validated pull request opened by Auto-fix, to confirmation that the corrected version is running everywhere the service runs. The same correlation feeds supply chain security and CI/CD security.

FAQ

What is build provenance?

Build provenance is a verifiable record of which source revision and build process produced an artifact. In the SLSA model it is an attestation naming the artifact's digest, the repository and commit it came from, and the builder that ran the build. It answers "where did this come from" for a container image, a binary or a package in a way an image tag cannot.

Is an image digest the same as build provenance?

No. A digest identifies the artifact's bytes exactly but says nothing about its source. Provenance is the record that links that digest to a repository and a commit. You need both: the digest to know what is running, and the provenance to know what it was built from.

What does org.opencontainers.image.revision do?

It is the standard OCI annotation key for the source control revision of the software in an image, normally a git commit SHA. Set it as a Dockerfile label from a CI build argument, alongside org.opencontainers.image.source for the repository URL, and any registry or scanner that reads the image config can trace the image back to its commit.

Do I need SLSA Build L3 to know which commit is in production?

No. Knowing which commit is running requires that provenance exist and be readable, which is SLSA Build L1. The higher levels add signing and tamper resistance, which matter when you have to prove the record was not forged. Record the commit on every artifact first and harden the record afterward.

How does Heeler match a container image to its source repository?

Heeler matches a container image to its source repository by build provenance, by its image label, by service correlation, by the deployment manifest, by a suggested name match, or by a manual link, and the image's detail page shows which one applied. An image whose tag or label is a git commit is matched to the repository whose history contains that commit, including short-SHA tags.

If "which commit is running in production" is a question your team answers with a spreadsheet and a chat thread, Heeler answers it from the running workload, the image and the repository, and tracks every fix until it is Deployed. Get a demo

What’s new on Heeler
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Related resources

See All Resources