Artifacts, Registries, Signing, and Provenance
What This Concept Is
Four layered ideas about what you ship and how you trust it.
- Artifacts -- the immutable output of a build: container images, jar/whl/npm tarballs, compiled binaries, Helm charts, Terraform modules. Identified by a digest (content hash), not just a name.
- Registries -- the system of record for artifacts. Container registries (GHCR, ECR, GCR, Docker Hub), language registries (npm, PyPI, Maven Central), internal registries. Artifacts in a registry are addressable, auditable, and can carry metadata.
- Signing -- cryptographic attestation that this specific artifact digest came from this specific identity (a maintainer, a build system). Tools: Sigstore
cosign, GPG, in-toto. Pro Git's Signing Your Work chapter introduces the same primitives for git -- signed commits and signed tags are the upstream half of the same chain. - Provenance -- machine-readable record of how the artifact was built: source repo, commit, builder identity, build steps, dependencies. The SLSA specification defines standard provenance levels.
Together they answer a single question: when we pull an artifact from the registry and run it in production, can we prove it came from the source we reviewed?
Why It Matters Here
The supply-chain attacks of 2020-2024 (SolarWinds, Codecov, dependency-confusion, xz-utils backdoor) all exploited a gap in this chain:
- the artifact in the registry was not the artifact the committer thought they were producing, or
- no mechanism existed to prove which source commit an artifact came from
- a compromised maintainer account pushed artifacts with no signature verification at install time
CI/CD pipelines sit exactly where these attacks land. A pipeline without signing and provenance is delivering code to production on trust that cannot be verified after the fact. The xz-utils incident (March 2024) was particularly instructive: a multi-year social-engineering operation on a maintainer, caught incidentally by a performance regression, not by any technical control -- which is exactly why provenance and admission policies are defense-in-depth, not replaceable by "we trust our maintainers."
Concrete Example: Signing a Container Image with Cosign
Build and sign in CI (GitHub Actions, using keyless OIDC signing):
- name: Build image
id: build
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/acme/api:${{ github.sha }}
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign image with keyless OIDC
env:
COSIGN_EXPERIMENTAL: '1'
run: |
cosign sign --yes \
ghcr.io/acme/api@${{ steps.build.outputs.digest }}
Verify at deploy time:
cosign verify \
--certificate-identity-regexp='https://github.com/acme/api/.+' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/acme/api@sha256:abcd...
The verification succeeds only if the signature was produced by a GitHub Actions workflow in the acme/api repo, under the repo's OIDC identity. An attacker with write access to the registry but no access to the repo cannot forge this.
Concrete Example: SLSA Provenance Levels
SLSA defines Build Tracks -- the most visible are Build Levels 1 through 3 (plus a planned Level 4):
| Level | Requirement | What it guarantees |
|---|---|---|
| Build L1 | Provenance exists | You know where the artifact came from (source repo, build system). |
| Build L2 | Provenance is signed by the build platform | The provenance itself is tamper-evident. |
| Build L3 | Build is done on hardened, isolated infrastructure | Build environment cannot be tampered with by the project's own code. |
A pipeline using GitHub Actions' slsa-github-generator produces SLSA Build L3 provenance by default -- the generator runs in a trusted reusable workflow that the project cannot modify from its own repo.
Attestation Types Beyond Provenance
The in-toto attestation framework (the underlying format SLSA uses) supports multiple predicate types. The ones you will see:
- SLSA Provenance -- how the artifact was built
- SBOM (CycloneDX or SPDX format) -- the full list of components, versions, and licenses inside the artifact
- VSA (Verification Summary Attestation) -- "this artifact was verified by policy engine X at time T"
- Test attestation -- "these tests ran on this artifact and passed"
A mature supply-chain posture collects several of these and makes admission policy depend on all of them -- not "signed" in isolation, but "signed + has SBOM + SBOM passes CVE scan + SLSA L3 provenance present."
Common Confusion / Misconception
"The image tag is its identity." It is not. Tags are mutable -- an attacker or a well-meaning teammate can re-point v1.2.3 at a different digest. The only stable identity is the content digest (sha256:...). Deploy by digest; use tags only as human-friendly aliases. The same identity-by-hash stance Pro Git takes for commits applies here.
"We sign with a key file checked into the repo." Please no. Long-lived signing keys are a supply-chain liability. Keyless signing via Sigstore/Fulcio uses short-lived certificates tied to OIDC identity (the workflow that ran, the user who ran it). No secret to leak.
"Signing proves the artifact is safe." It does not. Signing proves provenance, not security. A malicious commit that builds cleanly will produce a valid signature. Signing is a building block; it combines with reviewed PRs and dependency scanning to give safety.
"Provenance is just metadata." Under SLSA it is a specific, structured, machine-verifiable document. Tools can refuse to deploy artifacts without L2+ provenance. Treat it as a policy input, not a log file.
"We can't sign, we don't have a PKI." You do not need one. Sigstore (free, public) handles the CA and transparency log. Most public OSS and a growing share of enterprise code sign with Sigstore by default.
"Signed git tags and signed artifacts are duplicate work." They cover different segments of the chain. A signed git tag proves "this source tree, at this commit, was blessed by a named human." A signed artifact proves "this binary came from some CI build." Together: "this binary came from a CI run of a commit blessed by a maintainer." Pro Git's signing and tag-verification sections cover the git half.
How To Use It
For any service you deploy, close the chain end-to-end:
- Build once into a registry you control, tagged by commit SHA (see concept 4).
- Sign the resulting digest from CI using keyless OIDC.
- Generate SLSA provenance as part of the build.
- Generate an SBOM (Syft, Trivy, or
docker sbom) and attach it as an attestation. - Admission control at deploy time: refuse to run unsigned images. Kubernetes: Kyverno, Sigstore
policy-controller. Cloud: cosign-verifying admission in CI/CD or at runtime. - Registry policy: immutable tags for releases (cannot overwrite), retention policies for old digests, access logs.
- Source side: signed commits and tags (Pro Git) so the identity chain extends back to the maintainer.
Check Yourself
- Why is a digest a better identity than a tag for deployment?
- What does SLSA Build Level 2 guarantee that Level 1 does not?
- Keyless signing removes which specific supply-chain failure mode?
- Signing proves provenance. What does it not prove?
- What does an SBOM attestation add that a provenance attestation does not?
Mini Drill or Application
Pick one image your team uses. Answer:
- Is it pinned by digest or tag in deployment manifests?
- Is it signed? With what identity?
- Is there SLSA provenance attached to the artifact?
- Is there an SBOM attached?
- Does your cluster or deploy pipeline verify the signature before running?
For any "no," design the smallest change to turn it into a "yes." Most teams can get to signed + verified within one sprint.
Read This Only If Stuck
- Pro Git: Tagging (annotated and signed)
- Pro Git: Cleaning working directory / verifying tags
- Pro Git: Signing your work (commits and tags)
See also (external)
- SLSA v1.0 specification -- supply-chain integrity framework
- SLSA -- Build levels -- L1, L2, L3 requirements
- Sigstore -- Cosign -- keyless signing for containers
- in-toto attestation framework -- attestation formats that SLSA builds on
- OCI Image spec -- content addressable storage -- why digests, not tags, are the identity
- CycloneDX -- SBOM format -- common SBOM spec
- slsa-github-generator -- L3 provenance from GitHub Actions