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:
| Level | Requirement |
|---|---|
| SLSA 1 | Provenance is documented |
| SLSA 2 | Builds run on a hosted service |
| SLSA 3 | Hardened 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.
