A signed release proves that someone with a key signed it, not that it was built from the source you reviewed on a build system nobody tampered with. SLSA (Supply-chain Levels for Software Artifacts) and in-toto attestations close that gap by recording, in a signed, tamper-evident and machine-checkable form, which builder produced an artefact, from which source revision, with which parameters. This guide covers the SLSA v1.2 Build and Source tracks, the anatomy of a provenance attestation, keyless signing with Sigstore and the workload identities behind it, producing provenance on GitHub, npm and PyPI, hermetic and reproducible builds, and verifying provenance at deploy time with policy engines. It is for platform engineers, release engineers and application security teams. Dependency selection, SBOMs and malicious packages are outside its scope; pipeline credentials are covered in the CI/CD Pipeline Identity Security Guide.
Key takeaways
- SLSA v1.2 is the current approved specification and has two tracks: Build (L0 to L3) and the new Source track (L1 to L4).
- Provenance is only useful if someone verifies it against expectations: the builder identity, the source repository, the build type and the external parameters, with unknown parameters causing failure.
- Build L3 is mostly about identity and secrets: isolated runs, and provenance signing material that user-defined build steps cannot reach.
- Keyless signing moves trust to the builder's workload identity, so the OIDC trust configuration and the identity you pin at verification become the controls.
- Verify where it is enforceable: at registry upload, at install, or in a Kubernetes admission controller, starting in audit mode and moving to enforce once the false positives are gone.
What SLSA covers in v1.2
SLSA is an OpenSSF specification. The specification index lists v1.2 with status "Approved", alongside v1.1 and a working draft. The change log for v1.2 says the Source track was added, the threat model was updated to cover the threats it mitigates, and the specification was reorganised to support multiple tracks. No Build track changes are listed, so Build levels mean the same in v1.0, v1.1 and v1.2. Older material citing "SLSA 4" refers to the earlier v0.1 specification, which used a single track numbered 1 to 4.
Build track
| Level | Name | Core requirement | Threat it addresses |
|---|---|---|---|
| Build L0 | No guarantees | None: the absence of SLSA | None |
| Build L1 | Provenance exists | The build platform automatically generates provenance recording "what entity built the package, what build process they used, and what the top-level input to the build were" | "Mistakes, documentation": release errors, missing inventory, inability to rebuild |
| Build L2 | Hosted build platform | Builds run on a hosted platform that must "Generate and sign the provenance itself" | "Tampering after the build": altering the artefact in storage or a registry |
| Build L3 | Hardened builds | The platform must "prevent runs from influencing one another, even within the same project" and keep provenance signing secrets away from user-defined build steps | "Tampering during the build": insiders, stolen credentials, other tenants, cache poisoning between runs |
L1 provenance is unauthenticated, so its value is documentation. L2 is the first level that resists an attacker; L3 is the realistic target for anything shipped to customers or deployed to production.
Source track
| Level | Name | What the source control system must do |
|---|---|---|
| Source L1 | Version controlled | Store and manage source in a modern version control system |
| Source L2 | History and Provenance | Keep branch history "continuous, immutable, and retained", and issue a source provenance attestation for each new revision |
| Source L3 | Continuous technical controls | Continuously enforce the organisation's technical controls (for example required checks) on named branches and tags |
| Source L4 | Two-party review | "Changes in protected branches MUST be agreed to by two or more trusted persons prior to submission" |
The source control system communicates the level it has verified through a Source Verification Summary Attestation (VSA), which tells consumers "what high level security properties a given source revision meets". Two-party review may grant trusted robots, such as Dependabot, a standing exception, which makes those bot identities high-value targets in their own right. Note that the VSA verifiedLevels field enumerates Source levels 0 to 3 in v1.2, even though L4 is defined.
Anatomy of a provenance attestation
SLSA provenance is a predicate inside an in-toto attestation. The in-toto Attestation Framework has four layers: the Predicate "Contains arbitrary metadata about a subject artifact"; the Statement "Binds the attestation to a particular subject and unambiguously identifies the types of the predicate"; the Envelope "Handles authentication and serialization" (in practice usually a DSSE envelope); and the Bundle groups several attestations. The Statement type is https://in-toto.io/Statement/v1 and the SLSA predicate type is the exact string https://slsa.dev/provenance/v1, which verifiers should match as a string rather than resolve.
| Field | Meaning | What a verifier does with it |
|---|---|---|
subject[].digest | Artefacts the build produced, by name and hash | Must match the digest of the artefact being installed or deployed |
runDetails.builder.id | "URI indicating the transitive closure of the trusted build platform" | Looked up in the verifier's roots of trust to decide the maximum Build level |
buildDefinition.buildType | Template that says how to interpret parameters and dependencies | Must equal the expected build type |
buildDefinition.externalParameters | Parameters "under external control, such as those set by a user or tenant" | Untrusted: compare with expectations (repository, ref, workflow path); fail on anything unrecognised |
buildDefinition.internalParameters | Parameters set by the build platform itself | Optional; useful in incident response |
buildDefinition.resolvedDependencies | Artefacts needed at build time, as URIs with digests | Optional recursive verification; evidence for a hermeticity review |
runDetails.metadata | invocationId, startedOn, finishedOn | Links the artefact to a specific run and its logs |
runDetails.byproducts | Other outputs such as logs, not the build output | Debugging and forensics |
The same framework carries other predicates, such as SBOMs and test results. Provenance answers "where and how was this built", not "is it safe".
Where identity fits: keyless signing and builder identity
Every provenance signature is made by a non-human identity. Traditionally that was a long-lived private key stored as a CI secret, readable by any compromised build step. Sigstore replaces it with keyless signing:
- The build job generates an ephemeral key pair and requests an OIDC token from its CI platform. The token's claims describe the workload: repository, workflow, ref and trigger.
- Fulcio, the Sigstore certificate authority, verifies the token and "issues a short-lived certificate bound to the provided identity and public key". Fulcio certificates are "valid for 10 minutes".
- The job signs the attestation, discards the private key, and the signing event is recorded in Rekor, "an immutable, append-only ledger".
- Verifiers check the signature, that the certificate identity matches an expected identity, that the certificate chains to the Sigstore root, and the Rekor inclusion proof, which shows the certificate was valid when the signature was made.
- Pin the identity, not just the issuer: accepting any certificate from
https://token.actions.githubusercontent.comaccepts anything any GitHub repository built. Pin the repository and, for L3, the reusable workflow path. With Cosign that means--certificate-identity(or a tightly anchored--certificate-identity-regexp) plus--certificate-oidc-issuer. - Treat the OIDC trust configuration as privileged: the same job token that obtains a signing certificate can usually be exchanged for cloud credentials. Scope federation trust to repository, branch and environment. See the Cloud Workload Identity Guide.
- Keep signing material out of user-defined steps: the L3 requirement that signing secrets be inaccessible to build steps is the same principle as not exposing deployment secrets to pull request builds. Where you still sign with stored keys (air-gapped or private registries), hold them in a KMS or HSM and grant the signer a sign-only permission. See the Cryptographic Key Management Guide.
- Remove publish tokens once trusted publishing works: npm and PyPI trusted publishing exchange the CI job's OIDC identity for publication, so long-lived registry tokens can be revoked. See the Secrets Management Guide.
- Monitor the transparency log: an unexpected Rekor entry under your repository identity is a strong compromise signal.
Producing provenance on common platforms
| Platform | Mechanism | Key requirements and limits |
|---|---|---|
| GitHub Actions | Artifact attestations with actions/attest; signed with Sigstore | Permissions id-token: write, contents: read, attestations: write (plus packages: write for images). Public repositories on all current plans; private and internal repositories need GitHub Enterprise Cloud |
| GitHub Actions, L3 pattern | Build in a reusable workflow that also generates the attestation | GitHub documents this as helping "achieve SLSA v1.0 Build Level 3"; verify with --signer-workflow pinned to the reusable workflow. The OpenSSF slsa-github-generator uses the same pattern |
| npm | npm publish --provenance, or automatic with trusted publishing | GitHub Actions or GitLab CI/CD on "a supported cloud CI/CD provider using a cloud-hosted runner"; npm CLI 9.5.0 or later; the repository field must match the source; consumers run npm audit signatures |
| PyPI | PEP 740 attestations via Trusted Publishing | Publishers: GitHub Actions, GitLab CI/CD, Google Cloud, ActiveState. Two predicate types, PyPI Publish and SLSA Provenance; at most two attestations per file |
| Google Cloud Build | Build provenance and attestations consumed by Binary Authorization | Binary Authorization enforces policy at deploy time for GKE, Cloud Run and other runtimes, and offers an SLSA check in continuous validation |
| Self-hosted CI | Cosign or in-toto tooling called from the pipeline | Reaching L2 or L3 depends on the platform's own isolation and on keeping signing out of user steps; a self-hosted runner shared across projects will not meet L3 |
npm states the limit plainly: provenance does not guarantee a package is free of malicious code; it gives a verifiable link to source and build instructions.
Hermetic, isolated and reproducible builds
SLSA L3 requires isolation between runs. Two stronger properties sit beyond the current Build levels and are worth pursuing for your most critical artefacts:
- Ephemeral and isolated: each run starts in a fresh VM or container, with no cache or workspace shared with other runs or with untrusted pull request builds. Shared caches are a common way one run influences another.
- Hermetic: every input is declared in advance and pinned by digest, and the build has no network access once inputs are fetched. Hermeticity makes
resolvedDependenciescomplete rather than best effort, and stops a build from silently pulling a newly poisoned dependency or a retagged action. - Reproducible: "A build is reproducible if given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts." Relevant environment attributes include dependency versions, build flags and environment variables such as locale. Common fixes are normalising timestamps (the
SOURCE_DATE_EPOCHconvention), file ordering and build paths. - Independent rebuild: reproducibility only pays off when a second, separately administered builder rebuilds and compares hashes. That is the one control that detects a compromised build platform signing valid provenance for a tampered artefact.
Hermetic builds are achievable for most services with lockfiles and digest-pinned base images; full reproducibility is costly and usually justified only for widely distributed software, security agents and firmware.
Verifying provenance before use
The SLSA verification procedure has three steps. First, check the envelope signature against your roots of trust, confirm the subject digest matches the artefact, confirm the predicate type, and look up the builder's level. Second, "You SHOULD compare the provenance against expected values" for at least the builder identity, the canonical source repository, the build type and the external parameters, and "Any unrecognized externalParameters SHOULD cause verification to fail." Third, optionally verify dependencies recursively, using VSAs to record earlier results.
Forming expectations
- Defined in source: the repository declares its expected builder and workflow, and the package name is bound to that repository. Best for internal software.
- Defined by producer: the producer publishes expectations through an authenticated channel that resists unilateral change. Useful for suppliers.
- Trust on first use: accept the first observed values and alert on change. The pragmatic option for third-party open source packages.
Where to enforce
| Enforcement point | Example tooling | Strength | Weakness |
|---|---|---|---|
| Registry at upload | Package ecosystem checks; internal artefact repository policy | SLSA calls this "RECOMMENDED whenever possible"; one check protects every consumer | Needs control of the registry |
| CI before promotion | gh attestation verify, cosign verify-attestation, npm audit signatures | Easy to add; blocks bad artefacts before they reach production registries | Bypassed by anyone who deploys outside the pipeline |
| Kubernetes admission | Kyverno verifyImages with keyless attestors and SLSA attestation conditions; Sigstore policy-controller | Enforced at the last moment; covers manual deployments | Only covers what the cluster runs; webhook availability becomes a dependency |
| Managed cloud deploy policy | Google Binary Authorization attestors, dry-run mode and continuous validation | Blocked deployments logged to Cloud Audit Logs; continuous checks on running Pods | Provider-specific |
| Monitor | Service that continuously verifies and publishes results | Catches drift and retroactive findings | Detects rather than prevents |
A Kyverno rule pins a keyless attestor with subject, issuer and rekor.url, can require an attestation of type https://slsa.dev/provenance/v1 with conditions on predicate fields such as buildDefinition.buildType, and by default rewrites image tags to digests (mutateDigest). Deploy by digest regardless: a tag can be moved after verification.
Choosing a target level
- Small teams on hosted CI: turn on GitHub artifact attestations or registry trusted publishing for everything you release. That reaches Build L2 at almost no cost; move release builds into a reusable workflow for L3.
- Large organisations with many pipelines: standardise on a few central build templates that produce L3 provenance, and make "built by an approved template" the deploy-time rule, so expectations live in one place.
- Software vendors: publish provenance with every release, plus the builder identity and verification command customers need to form expectations.
- Consumers of open source: verify where the ecosystem supports it (npm, PyPI, signed container images), use trust on first use, and alert when a package that had provenance publishes a release without it.
- L3 versus reproducible: L3 gives breadth across all services; independent reproducible rebuilds give depth on a few critical artefacts and are the only one of the two that protects against the build platform itself.
Implementing build provenance
- Inventory release paths: every pipeline that produces something deployed or distributed, its runner type, and where signing keys or publish tokens live.
- Generate provenance (L1 to L2): enable artifact attestations or trusted publishing on hosted runners and deploy by digest. Nothing is enforced yet.
- Replace stored keys: move to keyless signing, remove signing keys and registry tokens from CI secrets, and tighten OIDC trust policies to repository, branch and environment.
- Harden for L3: move release builds into reusable workflows or central templates, use ephemeral runners, and stop sharing caches between trusted and untrusted runs.
- Verify in audit mode: add verification in CI and an admission policy with
failureAction: Audit(Kyverno) or dry-run (Binary Authorization). Review every failure for two to four weeks; most will be images from unattested pipelines or third parties. - Enforce in rings: enforce first on a non-production cluster, then production namespaces owned by teams already compliant, then everything. Define an exemption list with owners and expiry dates.
- Plan rollback and break-glass: rollback is switching the policy back to audit, a logged platform team action. Break-glass deployments must alert and be reviewed.
- Add hermetic and reproducible builds for the highest-risk artefacts once enforcement is stable.
Detection and measurement
- Admission and policy logs: denied and audited deployments, by namespace and image source.
- Transparency log monitoring: signing events in Rekor for your repository identities that do not correspond to a known pipeline run.
- CI audit logs: changes to release workflows, runner groups and OIDC trust policies.
- Registry events: publications made with tokens rather than trusted publishing, and releases that lack provenance when earlier ones had it.
Metrics that show it is working:
- Percentage of production deployments whose image digest has verified L3 provenance.
- Number of long-lived signing keys and registry publish tokens remaining in CI secrets (target zero).
- Admission policy mode coverage: share of clusters and namespaces in enforce rather than audit.
- Count and age of active exemptions and break-glass deployments.
- Percentage of third-party packages and base images in use that publish verifiable provenance.
- For critical artefacts, the share of releases independently rebuilt with matching hashes.
Common failure patterns
- Signing without verifying: provenance is produced and stored, but nothing checks it before deployment.
- Verifying the issuer but not the identity: a policy that accepts any GitHub Actions certificate, or a loose regular expression, lets an attacker's own repository produce "valid" provenance.
- Treating valid provenance as safe code: provenance shows where an artefact was built. If attacker code runs inside the legitimate workflow, the output carries genuine provenance. The Ultralytics PyPI compromise combined a GitHub Actions injection with a leftover PyPI token; workflow hardening and token removal are both needed.
- Compromised build platform: SLSA L3 trusts the builder. In the SolarWinds compromise malicious code entered during the build and shipped with a legitimate signature, the case independent reproducible rebuilds are designed to catch.
- Unverified downloads in pipelines: scripts piped from the internet into a shell bypass every attestation. The Codecov breach altered a widely used CI script to harvest secrets, and the exposure of the HashiCorp release signing key through it shows why signing material must not be reachable from build steps.
- Mutable inputs: builds that reference tags rather than digests for actions, base images and dependencies are not hermetic. The tj-actions/changed-files compromise repointed existing tags at malicious code.
Framework mapping
| Framework | Reference | How provenance contributes |
|---|---|---|
| NIST SSDF (SP 800-218) | PO.5, PS.2.1, PS.3.1, PS.3.2 | Secure build environments; integrity verification information for releases; archive releases; collect and share provenance data |
| NIST SP 800-53 Rev. 5 | SR-4, SR-11, SI-7, SI-7(15), CM-14, SA-10 | Provenance, component authenticity, software integrity, code authentication, signed components, developer configuration management |
| NIST CSF 2.0 | GV.SC, PR.PS-06 | Supply chain risk management; secure software development practices |
| ISO/IEC 27001:2022 Annex A | A.5.21, A.8.4, A.8.25, A.8.32 | ICT supply chain security, access to source code, secure development life cycle, change management |
| CIS Controls v8.1 | Control 16 | Application software security, including third-party component management |
| MITRE ATT&CK v20 | T1195.002 Compromise Software Supply Chain | Provenance verification and independent rebuilds detect manipulated software before it reaches the consumer |
Practitioner checklist
- Record the SLSA Build level of every release pipeline and set L3 as the target for production and customer-facing artefacts.
- Generate signed provenance for all release artefacts and deploy by digest, never by tag.
- Replace stored signing keys and registry publish tokens with keyless signing and trusted publishing, then revoke the old credentials.
- Scope OIDC federation and signing identities to specific repositories, branches, environments and reusable workflows.
- Verify provenance against expectations (builder, repository, build type, external parameters) and fail on unknown parameters.
- Run admission policies in audit mode first, then enforce in rings with owned, expiring exemptions.
- Monitor the transparency log, CI audit logs and registry events for signing or publishing outside known pipelines.
- Pin actions, base images and dependencies by digest, and isolate release builds from untrusted runs and shared caches.
- Pilot reproducible builds with an independent rebuilder for your most widely distributed artefacts.
Standards and references
- OpenSSF SLSA: Build Track Basics (v1.2)
- OpenSSF SLSA: Source Track Requirements (v1.2)
- OpenSSF SLSA: Verifying Artifacts (v1.2)
- in-toto: Attestation Framework Specification
- Sigstore: Overview
- GitHub Docs: Using artifact attestations to establish provenance for builds
- npm Docs: Generating provenance statements
- Reproducible Builds: Definitions
Related NHI Mgmt Group resources: CI/CD Pipeline Identity Security Guide · Cloud Workload Identity Guide · Secrets Management Guide · AI Supply Chain and AI-BOM Guide · Machine Identity, PKI and Certificate Lifecycle Guide