How to verify software provenance: npm, GitHub, Sigstore and SLSA

7 min read

To verify software provenance, you check a signed attestation against an identity you trust (a builder, a repository, a workflow) and confirm that the digest the attestation names matches the bytes you are holding. If either check fails, the provenance tells you nothing about that artifact.

npm packages, GitHub Actions builds and container images can all carry these attestations when the publisher turns them on, usually in the SLSA provenance format and signed through Sigstore. The commands differ by ecosystem, and they check the same three things to different depths.

Every provenance tool runs three checks

The SLSA specification’s verification steps reduce to three questions:

  1. Is the signature valid, and is the signer a builder you trust? A valid signature from a builder you have never heard of proves only that somebody signed something.
  2. Does the attestation describe this artifact? The attestation’s subject carries a digest, usually SHA-256. Hash your file and compare.
  3. Was it built the way you expected? The source repository, the tag or commit, and the build parameters should match what you meant to install. A correctly signed build of a fork is still a build of a fork.

The GitHub CLI, cosign and slsa-verifier do all three when you pass the right flags, and the third check is the one a command still passes without: leave out the flag and any build from anywhere is accepted. npm has no such flag, so on npm the third check is yours to do by hand.

SLSA also grades the builder. At Build L1 provenance exists; at L2 a hosted build platform signed it; at L3 the platform also keeps its signing keys out of reach of the build steps and isolates builds from each other. A check that passes tells you which builder signed, and the builder’s level tells you how much that signature is worth.

npm packages

npm audit signatures

Run it in a project with its dependencies installed, on npm 9.5.0 or later. It checks the registry signature on every installed package and the provenance attestation on every package that publishes one, and reports any signature or attestation that is invalid and any registry signature that is missing.

A package that publishes no attestation still passes, and you learn nothing about how it was built.

For the third check, open the package page on npmjs.com. It shows the source commit, the workflow file that built the package, and a link to the public transparency-log entry. Compare the repository and commit with the ones you meant to install. npm shows an error when the linked repository has since been deleted or made private, because the provenance can no longer be established.

Artifacts built on GitHub Actions

gh attestation verify ./release.tar.gz --repo owner/project
gh attestation verify oci://ghcr.io/owner/image:1.4.0 --owner owner

The command refuses to run without --repo or --owner, because that flag is your trust decision: it names whose workflows you accept as the builder. Pin it tighter with --signer-workflow for one workflow file, or --source-ref for one branch or tag. Add --deny-self-hosted-runners to reject builds that ran on machines GitHub does not operate. By default it looks for a https://slsa.dev/provenance/v1 predicate.

The command fetches attestations from GitHub, so it needs gh auth login and a network connection. For an offline check, download the attestation bundle and pass it with --bundle.

Container images signed with Sigstore

cosign verify-attestation \
  --type slsaprovenance1 \
  --certificate-identity-regexp '^https://github\.com/owner/project/\.github/workflows/release\.yml@refs/tags/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/owner/image@sha256:<digest>

A keyless Sigstore signature is bound to an identity in a short-lived certificate, so the two --certificate-* flags are where you say whom you trust. The regex above accepts one workflow file, run from a tag; a regex loose enough to match anything accepts a signature from anyone with a GitHub account. cosign also checks that the signing event is recorded in Rekor, Sigstore’s public transparency log. Pass the image by digest rather than by tag, because a tag can be moved between your check and your pull.

Release binaries from the SLSA GitHub generator

slsa-verifier verify-artifact project-linux-amd64 \
  --provenance-path project-linux-amd64.intoto.jsonl \
  --source-uri github.com/owner/project \
  --source-tag v1.4.0

slsa-verifier checks provenance produced by the SLSA GitHub generator, and --source-uri with --source-tag covers the third check. As of October 2026, its README says the project is no longer actively maintained and points GitHub Actions users at gh attestation verify. Use it for releases that still ship .intoto.jsonl files, and the GitHub CLI for anything new.

What a passing check proves

Check Proves Does not prove
Signature from a trusted builder identity A builder you trust produced this attestation That the builder itself was not compromised
Subject digest matches The attestation is about these exact bytes Anything about bytes you did not hash
Source repository, ref, build parameters It was built from the source you expected That the source is free of malicious code
Transparency-log entry The signing event was publicly recorded That anyone reviewed it

Build provenance answers one question well: where did this artifact come from?

Build provenance stops when the program starts

For most software, tests and code review cover what happens after start-up, because the code decides what the program does. An AI agent decides its actions at runtime, so tests cover far less of it. A coding agent or a support agent can arrive with perfect provenance, signed and built from the right commit by the right workflow, and still drop the wrong table on Tuesday. Nothing in a SLSA attestation describes Tuesday.

“What did it do?” needs a different record, written while the software runs: each action in order, with the authorization that admitted it, signed so that someone outside your company can check it. That is verifiable execution provenance, and Provetrail is an open draft standard for it (0.1.0-draft), with Flynn as the runtime that emits the records. It borrows the transparency-log design the tools above rely on: an append-only Merkle tree built the way Certificate Transparency builds its logs (RFC 9162), with the root signed in Ed25519 inside a COSE envelope.

The home page runs a signed execution record through the integrity check in your browser. Verify it, then flip a byte and watch the check fail. The steps that check performs are in how to verify an AI agent’s execution record, and the other kinds of provenance, from image credentials to data lineage, are sorted in provenance verification: four kinds.