Skip to content

Artifact Signing and Provenance

An SBOM tells you what is in an artifact; signing and provenance prove who built it and how, and that it has not been tampered with since. After incidents like SolarWinds — where attackers compromised the build system to inject malicious code into legitimately signed releases — verifiable build integrity has become a core supply-chain control.

The two guarantees

  • Signing — a cryptographic signature binds an artifact to its producer, so consumers can verify authenticity and integrity (the artifact is exactly what the publisher released, and has not been modified in transit or at rest).
  • Provenance — tamper-evident metadata describing how the artifact was built: the source commit, the builder identity, the build steps, the input parameters, and the environment. Provenance establishes a verifiable "chain of custody" from source to binary. Without provenance, you know what is signed but not that the build process was not compromised.

SLSA: levels of build integrity

SLSA (Supply-chain Levels for Software Artifacts) defines graded requirements that give concrete meaning to claims about provenance. The current specification is v1.2 (November 2025), which has two tracks: the Build track (levels 1–3, summarized below) and the Source track, which covers how source revisions are authored, reviewed, and retained (branch protection, review enforcement, continuous controls).

Build level Requirements Threat addressed
Level 1 Provenance exists and is available Accidental mistakes; basic auditability
Level 2 Provenance is signed and generated by a hosted build service Forgeable provenance; tampering after the build
Level 3 Hardened build platform; unforgeable provenance (signing material inaccessible to the build); runs isolated from other builds Compromised build infrastructure; tampering by other tenants or builds

Start at Level 1 (provenance document exists) and progress toward Level 2 (signed by hosted CI) as the default target. Level 3 is appropriate for critical infrastructure and software serving high-assurance consumers. Hermetic, reproducible builds are valuable additional hardening but are not Build track requirements in v1.x.

Sigstore: making signing practical

Historically, signing meant managing long-lived GPG keys — painful, error-prone, and a risk in themselves (a leaked signing key compromises all past and future releases). Sigstore changed this with keyless signing:

  • cosign — signs container images and arbitrary artifacts (binaries, SBOMs, attestations).
  • Keyless / OIDC signing — uses short-lived certificates tied to a workload identity (a CI job's OIDC token from GitHub Actions, GitLab CI, etc.), so there are no long-lived keys to store, rotate, or leak.
  • Rekor — records signatures in a tamper-evident, append-only transparency log; any signature can be independently verified without contacting the signer.
  • in-toto attestations — carry SBOMs, provenance, vulnerability scan results, and test outcomes as signed statements (predicates) about an artifact.

Signing a container image with keyless cosign in GitHub Actions

permissions:
  id-token: write   # lets the job request the OIDC token used for keyless signing
  packages: write

steps:
  - name: Install cosign
    uses: sigstore/cosign-installer@v4

  - name: Sign the container image
    run: cosign sign --yes myregistry/myimage@${{ steps.build.outputs.digest }}

The OIDC token identifies the signing workflow; Rekor records the certificate and signature. No key management required. Keyless signing is the default in cosign 2.x and later, so the old COSIGN_EXPERIMENTAL variable is no longer needed. Sign by digest and use the Sigstore public-good instance or a private deployment depending on whether signing identities may be public.

Generating SLSA provenance with the GitHub SLSA generator

# Reusable workflow (a job-level `uses:`); it must be referenced by a full release tag such as v2.1.0, not a branch, SHA, or floating major tag
provenance:
  permissions:
    actions: read
    id-token: write
    packages: write
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
  with:
    image: myregistry/myimage
    digest: ${{ steps.build.outputs.digest }}
    registry-username: ${{ secrets.REGISTRY_USER }}
    registry-password: ${{ secrets.REGISTRY_PASS }}

This produces a signed SLSA Build Level 3 provenance attestation attached to the image. On GitHub, artifact attestations offer a simpler route:

permissions:
  id-token: write
  attestations: write
  contents: read
  packages: write

- uses: actions/attest-build-provenance@v3
  with:
    subject-name: myregistry/myimage
    subject-digest: ${{ steps.build.outputs.digest }}
    push-to-registry: true

Attestations on their own give Build Level 2; Level 3 requires running the build in a reusable workflow so the build is isolated from the caller's workflow (see GitHub's guidance on achieving SLSA v1 Build Level 3).

Verify before you trust

Producing signatures and provenance only helps if you verify them downstream. Verification should be non-optional:

  • At release — verify signatures and provenance before promoting an artifact from CI to the production registry. An unverifiable artifact is rejected.
  • At admission/deploy — only signed, policy-compliant artifacts with acceptable provenance run in the cluster. Use an admission controller to enforce this automatically.
# Verify a container image's signature and provenance
cosign verify \
  --certificate-identity-regexp "https://github.com/myorg/myrepo/.github/workflows/release.yml" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  myregistry/myimage:v1.2.3

# Verify SLSA provenance attestation. Use --type slsaprovenance for v0.2 predicates (as emitted by
# the SLSA GitHub generator) or --type slsaprovenance1 for v1.x predicates (e.g., GitHub artifact attestations)
cosign verify-attestation \
  --type slsaprovenance \
  --certificate-identity-regexp "^https://github.com/slsa-framework/slsa-github-generator/" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  myregistry/myimage@sha256:<digest> | jq -r .payload | base64 -d | jq .

# Or use the dedicated verifier, which also checks source repo and builder identity
slsa-verifier verify-image myregistry/myimage@sha256:<digest> \
  --source-uri github.com/myorg/myrepo --source-tag v1.2.3
  • Use policy engines to require a minimum SLSA level for production workloads. Kyverno and the Sigstore policy-controller can enforce this at the Kubernetes admission layer.

Package-registry provenance and trusted publishing

For libraries you publish, prefer registry-native attestations over hand-managed keys or tokens: npm provenance (npm publish --provenance), PyPI trusted publishing with attestations, and Maven Central/RubyGems/crates.io trusted publishing all use CI OIDC identity instead of long-lived publish tokens, and let consumers verify which repository and workflow produced a release.

Common pitfalls and anti-patterns

  • Signing but never verifying — many teams add signing as a CI step but never enforce verification at deploy. Signing without verification is security theater.
  • Long-lived signing keys — a key stored in a CI secret or a vault that is valid for years is a single point of failure. Prefer keyless signing with OIDC workload identities.
  • Signing the wrong artifact reference — sign by digest (sha256:...), not by tag. Tags are mutable; a signed tag can point to a different image tomorrow.
  • Provenance that covers only the final build step — if the build process pulls unverified inputs (scripts, base images without provenance), the provenance is incomplete. Pin and verify build inputs (base images, build scripts, dependencies) and consider hermetic builds with declared inputs for the strongest assurance.
  • No policy enforcement at admission — without an admission controller checking signatures and provenance, a manually pushed or tampered image can bypass all signing requirements.

Maturity progression

Starter — Sign all release container images and binaries using cosign with keyless OIDC signing in CI. Store in Rekor. Verify manually during release promotion.

Intermediate — Produce SLSA Build Level 2 provenance using GitHub artifact attestations or equivalent (the SLSA GitHub generator provides Level 3). Attach SBOMs as in-toto attestations. Automate signature verification in the release pipeline gate. Deploy Kyverno or Sigstore policy-controller to require verified signatures for production workloads.

Advanced — Achieve SLSA Build Level 3 using isolated, hardened hosted builds (e.g., the SLSA generator or reusable workflows); adopt hermetic builds and SLSA Source track controls where assurance requirements justify them. Verify provenance build parameters (source repo, commit, workflow) in admission policy — not just that a signature exists. Track SLSA level and signing coverage as KPIs. Provide provenance documents to customers on request.

Metrics and KPIs

Metric Target
% of release artifacts with valid signatures 100%
% of release artifacts with SLSA provenance 100%
SLSA level achieved for production artifacts Level 2 minimum; Level 3 for critical systems
Unsigned deployments blocked by admission control Track count; target: 0 bypass
Time to detect a tampered artifact < 1 hour (via Rekor / admission control)

Tools1

Open-source

  • cosign (Sigstore) - The standard for container and artifact signing, including keyless OIDC signing and attestation management. The default choice for Kubernetes-native signing.
  • in-toto - Framework for cryptographically verifiable supply-chain attestations linking every step from source to artifact; the attestation format used by SLSA.
  • Kyverno - Kubernetes policy engine that verifies image signatures and attestations at admission; declarative YAML-based policies.
  • Notary Project (notation) - CNCF signing and verification standard for OCI artifacts using X.509 certificates; suited to organizations that need to anchor trust in their own PKI or a managed key service.
  • Sigstore policy-controller - Admission controller that enforces signature and attestation policies across a Kubernetes cluster.
  • SLSA GitHub Generator - Generates SLSA Build Level 3 provenance for GitHub Actions builds using a trusted, isolated signing workflow.
  • slsa-verifier - Verifies SLSA provenance for artifacts and images, checking the builder identity and the source repository and tag.

Commercial

  • Chainguard - Produces minimal container images that are signed and come with SLSA-backed provenance by default; eliminates the signing setup burden for base images.
  • GitHub Artifact Attestations - Built-in build provenance and signing for GitHub Actions; generates SLSA provenance attestations natively without additional workflow configuration.


  1. Listed in alphabetical order. ↩