Setting Up npm Provenance with GitHub Actions
npm provenance produces a signed, publicly verifiable attestation linking a published tarball to the exact source commit and CI workflow that built it. Consumers can confirm a package was built from the repository it claims, on a trusted CI runner, without taking the publisher's word for it. This page sets up provenance end to end from GitHub Actions: the OIDC permission, the publish flag, the sigstore attestation, and how to verify the result.
Why Provenance
A standard npm publish proves only that someone with a token uploaded a tarball. Provenance adds a cryptographically signed statement — anchored in the public sigstore transparency log — asserting this tarball was built from this commit by this workflow. That closes the gap between "the source on GitHub" and "the bytes on the registry," and it is a foundational building block for the broader controls in Supply-Chain Security Hardening.
Prerequisites
- The package is published to the public npm registry (provenance requires public publishing).
- The repository is public, or you accept that provenance metadata references the repo.
- You publish from GitHub Actions (the OIDC issuer npm trusts). The end-to-end publish mechanics are in npm Registry Publishing Workflows.
- A recent npm CLI (provenance support landed in npm 9.5+; use Node 20's bundled npm or newer).
Numbered CI Setup
- Grant the workflow an OIDC token. Provenance is signed using GitHub's OIDC identity, which requires the
id-token: writepermission at the job (or workflow) level. Without it the publish aborts at the attestation step. - Pin the registry in
setup-nodeso the auth line is generated and the registry is unambiguous. - Build deterministically before publishing so the attested artifact matches the committed source.
- Pass
--provenance(or setpublishConfig.provenance: true) on the publish step. - Provide the publish token through
NODE_AUTH_TOKEN, never echoed.
Manifest opt-in (optional but recommended)
Pinning provenance in the manifest means a local or alternate publish path cannot silently drop it:
{
"publishConfig": {
"access": "public",
"provenance": true
}
}
Full workflow
# .github/workflows/publish-provenance.yml
name: Publish with provenance
on:
push:
tags:
- 'v*'
permissions:
contents: read
id-token: write # mandatory: lets npm fetch an OIDC token for signing
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org/'
cache: 'npm'
- name: Install
run: npm ci
- name: Build
run: npm run build
- name: Verify tarball contents
run: npm pack --dry-run
- name: Publish with provenance
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
The id-token: write permission and the GitHub-hosted runner together let npm exchange GitHub's OIDC token for a short-lived sigstore signing certificate. The CLI then records a signed attestation (build metadata, source commit, workflow path) in the public transparency log and uploads it alongside the tarball.
Validation
After the workflow runs, confirm the attestation exists and verifies:
# Show the published version's metadata (provenance is recorded in the registry)
npm view @acme/widget
# Verify attestations for the installed package against sigstore
npm audit signatures
npm audit signatures checks both the registry signature and any provenance attestations for installed packages and reports how many verified. On the registry web UI, a package published this way shows a "provenance" / verified-build indicator linking back to the exact workflow run and commit.
Guardrails
- Keep
id-token: writescoped to the publish job only — do not grant it workflow-wide if other jobs do not need it. - Set
publishConfig.provenance: trueso provenance is the default and cannot be dropped by an ad-hoc publish. - Publish only from tag-triggered workflows on protected branches so the attested commit is always a reviewed, tagged release.
- Pin action versions (
@v4) and the Node version so the build environment recorded in the attestation is reproducible. - Layer provenance with the build-integrity controls in Adding SLSA Provenance to Package Releases for full supply-chain coverage.
Frequently Asked Questions
Why does my publish fail with a provenance error even though I added --provenance?
The job almost certainly lacks the id-token: write permission. Provenance is signed with GitHub's OIDC identity, so without that permission npm cannot obtain the signing token and aborts before upload. Add permissions: id-token: write to the job.
Can I generate npm provenance from CI providers other than GitHub Actions? npm provenance currently trusts a specific set of OIDC issuers, with GitHub Actions and GitLab CI being the supported, documented paths. From an unsupported runner the attestation cannot be produced; publish from a supported provider instead.
Does provenance work for private or scoped-restricted packages?
No. Provenance requires public publishing, because the attestation is recorded in a public transparency log. For a scoped package, ensure access is public; see Publishing Scoped Packages to npm.
How do consumers verify the provenance of a package they installed?
They run npm audit signatures, which validates registry signatures and provenance attestations for installed packages against sigstore and reports how many verified. The registry web UI also surfaces a verified-build indicator linking to the originating commit and workflow run.
Related
- Adding SLSA Provenance to Package Releases — extend attestation to the full SLSA build-integrity framework.
- Supply-Chain Security Hardening — the broader set of controls provenance fits into.
- Fixing npm publish 403 Forbidden Errors — diagnose publish-step failures, including missing OIDC permissions.