Artifact Signing and Provenance

By Davy Rogers

How do you know that binary is what it claims to be? Sigstore and SLSA.

You build an artefact, it travels through a registry, and someone deploys it later. Between build and deploy, how does the deployer know it's the exact thing you built, and not something swapped in along the way? That gap is precisely where supply chain attacks operate: modify the artefact after the build, before the install. Signing and provenance close the gap by letting you prove three things about a binary: it's authentic, it's unmodified, and you can trace where it came from.

Sign without managing keys: Sigstore

The old objection to signing was key management, since long-lived signing keys are themselves a liability. Sigstore removes that by being keyless.

# Sign
cosign sign ghcr.io/myorg/myapp@sha256:abc123...

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

Two pieces make it work. Fulcio issues a short-lived certificate that binds the signing key to an OIDC identity, so the signature is tied to who built it. Rekor is an append-only transparency log, so a signature can't be quietly backdated or hidden.

Wire it into CI

Keyless signing fits naturally into a pipeline, because the OIDC identity is already there:

permissions:
  id-token: write  # Required for keyless signing

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

npm has its own provenance built on the same foundations:

npm publish --provenance
npm audit signatures

Provenance and SLSA

Signing proves who; provenance proves how. SLSA (Supply-chain Levels for Software Artefacts) is a ladder of increasing assurance about your build process:

LevelRequirement
SLSA 1Provenance is documented
SLSA 2Builds run on a hosted service
SLSA 3Hardened platform, non-falsifiable provenance

You don't have to leap to the top. Each rung makes it harder to forge a build.

Signing is pointless without verification

This is the step teams skip, and skipping it makes the whole exercise theatre. If you sign artefacts but deploy unsigned ones anyway, you've gained nothing. Enforce verification at the gate, so an unsigned image is simply refused:

# Kyverno - reject unsigned images
verifyImages:
  - imageReferences: ["ghcr.io/myorg/*"]
    attestors:
      - keyless:
          issuer: "https://token.actions.githubusercontent.com"

What to sign

Anything that crosses a trust boundary: container images, npm packages, Go binaries, Helm charts, even your SBOMs. If a deployer or another team will trust it later, sign it now, and make sure something on the other side actually checks the signature.