JustAppSec

Artifact Signing and Provenance

Sigstore, SLSA, and proving your software is what you say it is.

0:00

When you download a library, pull a container image, or install a binary, how do you know it came from the source you trust and was not modified in transit? Artefact signing and provenance records solve this. This lesson covers Sigstore, SLSA, and the practical mechanics of proving your software is trustworthy.

The problem

Supply chain attacks modify artefacts after they are built but before consumers install them. Examples:

  • A compromised package registry serves a backdoored version of a popular library
  • A CI pipeline is modified to inject malicious code into the build output
  • A man-in-the-middle replaces a binary during download

Without signing and provenance, consumers have no way to verify that an artefact is:

  1. Authentic — produced by the claimed author or organisation
  2. Unmodified — identical to what was built
  3. Traceable — linked to specific source code, build configuration, and infrastructure

Signing fundamentals

How signing works

  1. Build produces an artefact (container image, npm package, binary)
  2. A cryptographic hash (digest) of the artefact is computed
  3. The hash is signed with a private key
  4. The signature is published alongside the artefact (or in a transparency log)
  5. Consumers verify the signature against the public key before using the artefact

Traditional signing vs keyless signing

AspectTraditional (GPG / PGP)Keyless (Sigstore)
Key managementYou generate, store, rotate, and protect long-lived keysEphemeral key pair generated per signing event
IdentityKey fingerprint (opaque)Verified OIDC identity (email, CI workload)
RevocationComplex (key revocation lists)Short-lived certificates — no revocation needed
Trust rootWeb of trust or manual key exchangeCertificate transparency log (Rekor)
Adoption barrierHigh (key ceremonies, secure storage)Low (works with existing CI OIDC)

Traditional signing is still used (Debian packages, GPG-signed Git tags), but keyless signing via Sigstore is becoming the standard for CI/CD artefacts.

Sigstore

Sigstore is an open-source project that provides free, keyless signing for software artefacts. Its components:

Cosign — container image signing

# Sign a container image (keyless, uses OIDC identity)
cosign sign ghcr.io/myorg/myapp@sha256:abc123...

# Verify a signed image
cosign verify ghcr.io/myorg/myapp@sha256:abc123... \
  --certificate-identity=https://github.com/myorg/myapp/.github/workflows/build.yml@refs/heads/main \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com

When you run cosign sign in CI, Sigstore:

  1. Requests an OIDC token from the CI platform (e.g., GitHub Actions)
  2. Exchanges the OIDC token for a short-lived signing certificate from Fulcio (Sigstore's CA)
  3. Signs the image digest
  4. Records the signature and certificate in Rekor (the transparency log)
  5. The ephemeral private key is discarded

No long-lived keys to store or rotate.

Fulcio — certificate authority

Fulcio issues short-lived certificates (typically valid for 10 minutes). The certificate binds a signing key to an OIDC identity — for example:

  • subject: https://github.com/myorg/myapp/.github/workflows/release.yml@refs/tags/v1.2.0
  • issuer: https://token.actions.githubusercontent.com

This means: the artefact was signed by the release.yml workflow running on the v1.2.0 tag.

Rekor — transparency log

Rekor is an append-only, tamper-evident log of all signing events. Even after the short-lived certificate expires, the Rekor entry provides a permanent, verifiable record that the signature was valid at signing time.

Signing in CI/CD

GitHub Actions — container image signing

name: Build and sign
on:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write  # Required for keyless signing

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - id: build
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}

      - uses: sigstore/cosign-installer@v3

      - run: cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

npm package signing

npm supports Sigstore-based provenance attestation:

steps:
  - uses: actions/setup-node@v4
    with:
      node-version: 22
      registry-url: https://registry.npmjs.org

  - run: npm publish --provenance
    env:
      NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

The --provenance flag generates a signed SLSA provenance statement and publishes it to the npm registry. Users can verify it:

npm audit signatures

SLSA — Supply-chain Levels for Software Artefacts

SLSA (pronounced "salsa") is a framework that defines increasing levels of supply chain integrity guarantees.

SLSA levels

LevelRequirementWhat it prevents
SLSA 1Build process documented, provenance generatedUntracked builds
SLSA 2Provenance generated by a hosted build service (not the developer's laptop)Source tampering after commit
SLSA 3Hardened build platform, non-falsifiable provenanceCompromised build process
SLSA 4Two-party review, hermetic buildsInsider threats

Generating SLSA provenance

The SLSA GitHub generator creates provenance attestations:

jobs:
  build:
    outputs:
      digest: ${{ steps.hash.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
      - run: go build -o myapp
      - id: hash
        run: echo "digest=$(sha256sum myapp | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
      - uses: actions/upload-artifact@v4
        with:
          name: myapp
          path: myapp

  provenance:
    needs: build
    permissions:
      actions: read
      id-token: write
      contents: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
    with:
      base64-subjects: ${{ needs.build.outputs.digest }}

Verifying SLSA provenance

slsa-verifier verify-artifact myapp \
  --provenance-path myapp.sigstore.jsonl \
  --source-uri github.com/myorg/myapp \
  --source-tag v1.2.0

This checks:

  • The artefact matches the digest in the provenance
  • The provenance was signed by the expected CI platform
  • The build was triggered from the expected source and tag

Verification at deployment

Signing is useless if nothing verifies. Enforce verification at the point of consumption.

Kubernetes admission control

# Kyverno policy — reject unsigned images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-cosign-signature
      match:
        any:
          - resources:
              kinds: ["Pod"]
      verifyImages:
        - imageReferences: ["ghcr.io/myorg/*"]
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/myorg/*"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

Other admission controllers that support image verification:

  • Sigstore Policy Controller — native Sigstore integration
  • OPA Gatekeeper with signature verification
  • Docker Content Trust (Notary v2)

Dependency lock file verification

For language packages, verify digests match what was published:

# npm — verify signatures of installed packages
npm audit signatures

# pip — verify hashes
pip install --require-hashes -r requirements.txt

What to sign

ArtefactToolRegistry / Log
Container imagesCosignOCI registry + Rekor
npm packagesnpm publish --provenancenpm registry
Python packagessigstore-pythonPyPI
Go binariesSLSA GitHub generatorRekor
Helm chartsCosignOCI registry
SBOMsCosign attestRekor

Sign everything that crosses a trust boundary — anything that is built in one environment and consumed in another.

Summary

Artefact signing and provenance close the gap between "code was committed" and "software was deployed." Use Sigstore's keyless signing in CI (Cosign + Fulcio + Rekor) — no long-lived keys to manage. Adopt SLSA provenance to document exactly how artefacts were built and from which source. Most importantly, enforce verification at deployment using admission controllers or package audit tools. Signing only matters when verification is mandatory.


This training content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly. Send corrections to [email protected].