SLSA Provenance: Hardening Your CI/CD Supply Chain Step by Step

Khimananda Oli 8 min read DevOps
SLSA Provenance: Hardening Your CI/CD Supply Chain Step by Step

By Khimananda Oli | Last reviewed: September 2026

Supply chain attacks no longer target just your application code; they target the build process itself. Implementing SLSA Provenance: Hardening Your CI/CD Supply Chain Step by Step is the definitive defense against artifact tampering and compromised dependencies in 2026. While many teams generate Software Bills of Materials (SBOMs), few actually cryptographically bind that metadata to the build environment, leaving a critical trust gap. This guide moves beyond theory to show you exactly how to configure, sign, and verify provenance in production pipelines.

What is SLSA Provenance and why does it matter for supply chain security?

SLSA (Supply-chain Levels for Software Artifacts) Provenance is not merely a log file; it is a verifiable statement of fact about your build. In my work auditing SOC 2 compliance for fintech clients, I frequently find that while teams have logs, they lack integrity. A standard CI log can be modified by anyone with write access to the storage backend. SLSA Provenance solves this by separating the generation of metadata from the signing of metadata, ensuring that even if a developer account is compromised, the build record remains immutable.

The core value lies in the "non-falsifiable" requirement of SLSA Level 3. The build service generates the provenance directly, and the user-controlled build script cannot influence its contents. This prevents attackers from injecting malicious code and then forging a clean build record to cover their tracks. For teams managing critical infrastructure or handling sensitive data, this distinction between "logged" and "attested" is the difference between passing an audit and surviving a breach.

Source RepoCommit SHABranch RefTrusted Builder (Isolated)Build LogicUser ScriptProvenance GenSystem ControlledSigstore SignerOIDC IdentityArtifact + .intotoBinary / ContainerSigned AttestationUser scripts cannot modify provenance content
SLSA Provenance architecture isolates metadata generation from user-controlled build scripts to ensure integrity.

If you are new to securing pipeline secrets as part of this hardening process, review handling secrets in CI/CD pipelines safely before implementing provenance, as key management is foundational to trust.

How do you generate SLSA provenance in GitHub Actions?

GitHub Actions is currently the most mature ecosystem for SLSA Level 3 provenance because of its OIDC integration with Sigstore Fulcio. You should never write your own provenance generator; instead, use the official reusable workflows provided by the SLSA project. These workflows run in an isolated job context that your repository's workflow file cannot tamper with.

Configuring the Generic Generator

For most compiled binaries or archives, the generic generator is the correct choice. Add the following job to your existing workflow. Note that permissions must be explicitly set to allow ID token writing and package access.

<!-- .github/workflows/release.yml -->
name: Build and Sign
on: [push]

permissions: read-all

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      binary-hash: ${{ steps.hash.outputs.hash }}
    steps:
      - uses: actions/checkout@v4
      - name: Build artifact
        run: make build
      - name: Generate SHA256 hash
        id: hash
        run: sha256sum dist/myapp > checksum.txt
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: myapp-binary
          path: dist/myapp

  provenance:
    needs: [build]
    permissions:
      id-token: write   # Required for OIDC signing
      contents: read    # Required to read repo metadata
      actions: read     # Required to read workflow info
    uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
    with:
      base64-subjects: "${{ needs.build.outputs.binary-hash }}"
      upload-assets: true # Optional: attach to release

A common mistake I see in 2026 is teams trying to pass the artifact file directly to the provenance generator. Do not do this. Pass only the digest. The generator does not need the binary itself to create the attestation; it needs the hash to bind the signature to the specific output. Passing files increases attack surface and wastes bandwidth.

Container Image Provenance

If you are building Docker images, use the container-specific generator. It integrates directly with the registry to push the attestation alongside the image tag. This is critical for Kubernetes deployments where admission controllers like Kyverno or OPA Gatekeeper will verify signatures at pull time. For teams deploying to EKS or GKE, see Amazon EKS practical guide or Google GKE practical guide for cluster-specific policy enforcement details.

How do you verify SLSA provenance before deployment?

Generating provenance is only half the battle. Verification must be automated and mandatory. In practice, I recommend integrating verification into three distinct gates: the CI pipeline (post-build), the artifact registry (admission), and the runtime environment (pre-deploy).

CLI Verification with slsa-verifier

The slsa-verifier tool is the standard for checking attestations. Install it in your deployment runner and fail the pipeline if verification returns a non-zero exit code.

# Verify a binary against its provenance
slsa-verifier verify-artifact \
  --provenance-path myapp.intoto.jsonl \
  --source-uri github.com/myorg/myrepo \
  --builder-id https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.0.0 \
  dist/myapp

# Verify a container image
slsa-verifier verify-image \
  --source-uri github.com/myorg/myrepo \
  ghcr.io/myorg/myapp:v1.2.3

Notice the explicit --builder-id flag. This is non-negotiable. Without pinning the builder ID, an attacker could use a malicious fork of the generator to sign your artifacts. Always verify both the source repository AND the builder identity. This dual-check ensures the artifact came from your code AND was built by the trusted SLSA infrastructure.

Deployment TriggerVerify Signature & Builder IDslsa-verifier / Policy EngineFAILPASSBlock DeploymentAlert Security TeamQuarantine ArtifactProceed to DeployLog Verification EventUpdate Compliance DBRuntime ExecutionVerification must occur at EVERY stage boundary
Automated verification gate blocking unverified artifacts from reaching production environments.

What is the difference between SLSA Level 2 and Level 3 provenance?

Understanding the levels prevents over-engineering or under-securing your pipeline. Most teams should target Level 3 immediately, as the marginal effort over Level 2 is low when using managed CI systems.

FeatureSLSA Level 2SLSA Level 3
Build ServiceHosted, but not necessarily isolatedHardened, isolated per-build environment
Provenance GenerationCan be generated by user scriptMUST be generated by trusted builder service
Tamper ResistanceSigned, but content potentially influenceableNon-falsifiable; user cannot inject fields
Signing KeyMay be user-managedEphemeral, bound to workload identity (OIDC)
Audit ValueBasic traceabilityForensic-grade integrity evidence
Recommended ForInternal tools, low-risk librariesProduction apps, regulated systems, public OSS

In my experience helping Nepali fintechs achieve ISO 27001 certification, Level 3 is often the minimum acceptable standard for financial software. Auditors specifically look for the "non-falsifiable" property. If your build script can write to the provenance file before signing, you have a Level 2 attestation at best, regardless of what the metadata claims. Always verify the builder ID matches a known Level 3 generator.

How do you integrate SLSA verification with Kubernetes admission control?

Verifying at deploy time is too late for high-velocity teams. Push verification left into the cluster using policy engines. This creates a defense-in-depth layer where even a compromised CI pipeline cannot deploy unsigned artifacts.

Kyverno Policy Example

Kyverno has native support for verifying Sigstore bundles. The following policy blocks any pod that references an image without valid SLSA Level 3 provenance from your specific builder.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-slsa-provenance
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-slsa-level3
      match:
        resources:
          kinds: ["Pod"]
      verifyImages:
        - imageReferences:
            - "ghcr.io/myorg/*"
          attestors:
            - entries:
                - keyless:
                    url: https://fulcio.sigstore.dev
                    roots: |-
                      -----BEGIN CERTIFICATE-----
                      # Sigstore root CA here
                      -----END CERTIFICATE-----
                    issuer: https://token.actions.githubusercontent.com
                    subject: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.0.0"
          attestations:
            - predicateType: https://slsa.dev/provenance/v1
              conditions:
                - all:
                    - key: "{{ buildDefinition.buildType }}"
                      operator: Equals
                      value: "https://slsa-framework.github.io/github-hosted/runners/v1"

This configuration enforces that the OIDC issuer is GitHub Actions and the subject is the official SLSA generator. Any deviation fails admission. Combine this with Kubernetes RBAC hardening to ensure only the admission controller can bypass these checks during emergency break-glass procedures.

Traditional CI Trust ModelDeveloper Controls EverythingScript writes logs, signs artifacts, uploadsShared Secrets / Long-lived KeysCompromise one key = forge all historyLogs ≠ ProofMutable storage, no cryptographic bindingTRUST: Implicit & FragileSLSA Verified Supply ChainIsolated Trusted BuilderUser script CANNOT modify provenanceEphemeral OIDC SigningNo long-lived keys, identity-bound certsCryptographic AttestationTamper-evident, machine-verifiable proofTRUST: Explicit & Verifiable
Traditional implicit trust versus SLSA explicit verification model comparison for supply chain security.

Implementing SLSA Provenance: Hardening Your CI/CD Supply Chain Step by Step

Adopting SLSA is an iterative process. Start with your highest-risk artifact—usually your primary production API or customer-facing frontend. Get Level 3 provenance working there first, verify it manually, then automate the gate. Only after that pipeline is stable should you expand to libraries and internal tools. Remember that provenance is only as strong as your weakest link; if you sign artifacts but skip verification at deployment, you have spent effort for zero security gain.

Supply chain security is not a feature you enable once; it is a discipline you maintain. As you mature, combine SLSA with SBOMs and vulnerability scanning for comprehensive coverage. If your team needs help designing a compliant, hardened pipeline architecture or auditing your current supply chain posture, reach out to discuss your specific requirements.

Frequently Asked Questions

It is a cryptographically signed attestation describing how software artifacts were built, including source repository, build system, and dependencies.

Start with SLSA Level 2 to establish authenticated provenance and hosted builds before attempting hermetic Level 3 requirements.

Yes, signing and uploading attestations typically adds ten to thirty seconds per artifact depending on key management overhead.

Technically yes, but self-hosted runners struggle to meet isolation requirements. GitHub Actions or GitLab CI provide better native support for verifiable build environments needed for higher SLSA levels.

Use the slsa-verifier CLI tool to validate signatures against public keys and confirm builder identity matches your expected CI/CD infrastructure before promoting artifacts to production environments.

SBOMs list component ingredients while provenance documents the build process itself. Both are complementary supply chain security artifacts required for comprehensive audit trails and compliance verification.

Absolutely. Every build producing new artifacts requires fresh provenance reflecting current dependency versions, commit hashes, and builder configurations to maintain accurate supply chain records.

Most implementations use Sigstore Fulcio for keyless signing and Rekor for transparency logging. This eliminates long-lived key management while providing tamper-evident build history accessible to verifiers.

Not directly, but it exposes unexpected package sources during verification. Combined with private registry controls, provenance helps detect when malicious packages replace legitimate internal dependencies in build outputs.

Fail the pipeline immediately and investigate. Common causes include expired certificates, mismatched builder identities, or tampered artifacts. Never deploy unverified builds regardless of urgency or rollback pressure.

Yes. Tools like ko and docker/build-push-action support attaching provenance to OCI images. Store attestations alongside images in registries supporting referrers API or separate artifact repositories.

Generate separate provenance for each platform-specific artifact, then create an index attestation linking them. Verify each architecture independently since build environments and dependencies often differ across targets.

Minimal. Individual attestations are typically under five kilobytes. Even at thousands of daily builds, storage costs remain negligible compared to compute expenses for signing operations.

Difficult but possible using external signing services. However, achieving Level 2+ requires build system modifications for isolation and authentication that older platforms rarely support without extensive custom integration work.

With keyless Sigstore workflows, rotation is automatic per-signature. For traditional PKI approaches, rotate signing keys quarterly and maintain revocation lists to limit exposure from compromised credentials.