Verifying Package Provenance with npm audit signatures
Use npm audit signatures to verify registry signatures and Sigstore provenance for installed dependencies, and fail CI when attestations are missing.
Installing a dependency does not tell you whether its maintainer’s account was compromised, whether the tarball matches the source on GitHub, or whether a registry mirror silently served a different file than what was published. Running npm audit signatures after npm install surfaces exactly that gap:
$ npm audit signatures
audited 342 packages
1 package has an invalid signature:
[email protected] was not signed and could not be verified
284 packages have verified registry signatures
57 packages have no verified provenance attestation
This guide covers reading that output correctly, and turning it into a CI gate that fails a build when a signature is invalid — as opposed to merely absent, which is common and not itself a failure.
Root Cause
Two independent supply-chain checks exist inside npm, and conflating them leads teams to either over-trust or over-block their dependency tree. Provenance attestation is opt-in and proves a specific CI workflow produced the tarball; it is only present on packages whose maintainers published with --provenance. Registry signature verification is far more widespread — nearly every package published through npmjs.org in the last several years carries a signature the registry itself generates and can validate against its own public key. npm audit signatures reports on both simultaneously, and an invalid registry signature (not merely a missing provenance attestation) is the condition that indicates real tampering risk.
Minimal Reproduction
Any project with a package-lock.json and an installed node_modules can run the check — no special configuration is required to trigger a report:
mkdir demo && cd demo
npm init -y
npm install [email protected] [email protected]
npm audit signatures
Because dependency signature and provenance coverage varies package by package based on when and how each was published, the mix of verified, unverified, and attested results will differ across projects — the important distinction is between “no provenance” (common, low severity) and “invalid signature” (rare, high severity).
How the Verification Check Resolves Each Dependency
Step-by-Step Fix
Step 1 — Run the check locally and read the summary lines
npm audit signatures
The three summary counts to distinguish:
284 packages have verified registry signatures # good — most packages
57 packages have no verified provenance attestation # informational, not a failure
1 package has an invalid signature # actionable — investigate immediately
Expected result: you can identify which category [email protected] falls into and confirm it is the invalid-signature case, not merely an unattested one.
Step 2 — Investigate an invalid signature before continuing
npm view [email protected] dist.integrity
npm view [email protected] dist.signatures
Compare the returned integrity hash against what your lockfile records:
node -p "require('./package-lock.json').packages['node_modules/left-pad'].integrity"
Expected result: if the two values differ, the installed tarball does not match what the registry currently serves for that version — treat this as a potential tampering or cache-poisoning event and do not proceed with the install until resolved, typically by clearing the local cache with npm cache clean --force and reinstalling.
HAZARD PREVENTION
Symptom:
npm audit signaturesreports an invalid signature that disappears after clearing the cache and reinstalling.Root cause: A corrupted or partially-written tarball in the local npm cache, not an actual registry-side tampering event.
Fix: Run
npm cache clean --forcefollowed by a freshnpm cibefore escalating. If the invalid signature persists after a clean install from the public registry, treat it as a genuine security incident.
Step 3 — Add the check as a required CI step
- run: npm ci
- run: npm audit signatures
Expected result: npm audit signatures exits non-zero when any package has an invalid signature, failing the job automatically — no additional flag is required for this baseline behavior.
Step 4 — Optionally require provenance for your own scoped packages
To enforce provenance specifically for packages your organization publishes, without blocking on third-party dependencies that have not yet adopted it, script a targeted check:
#!/usr/bin/env bash
set -euo pipefail
for pkg in $(npm ls --json --depth=0 | node -pe "Object.keys(JSON.parse(require('fs').readFileSync(0,'utf8')).dependencies).filter(n => n.startsWith('@acme/'))"); do
url=$(npm view "$pkg" dist.attestations.url 2>/dev/null || echo "")
if [ -z "$url" ]; then
echo "Missing provenance attestation for internal package: $pkg"
exit 1
fi
done
Expected result: the build fails only when an @acme/* package lacks an attestation, while third-party dependencies without provenance do not block the pipeline.
Verification
npm ci
npm audit signatures
echo "exit code: $?"
Expected output and exit code on a clean dependency tree:
audited 342 packages
341 packages have verified registry signatures
1 package has no verified provenance attestation
exit code: 0
An exit code of 1 indicates at least one invalid signature was found and should stop a CI pipeline before any build or deploy step runs.
Edge Cases / Gotchas
- Monorepos with workspaces report signatures per-workspace; run
npm audit signaturesfrom the repository root so it covers the full deduplicatednode_modulestree, not just one package’s subset. npm audit signaturesrequires network access to the public registry to fetch current signing keys — air-gapped CI runners need a periodically refreshed local key cache or the check will fail closed.- Private/scoped registries (Verdaccio, Artifactory, GitHub Packages) proxy signatures inconsistently; verify your specific registry mirror preserves upstream signature metadata rather than stripping it during proxying.
- Yarn and pnpm do not currently expose an equivalent single command; teams on those package managers typically shell out to
npx npm audit signaturesagainst the resolvednode_modulestree, or rely on Sigstore’s owncosign verify-attestationtooling directly. - Lockfile drift between
package-lock.jsonand installednode_modulescan cause the check to report against stale version numbers — always runnpm ci, notnpm install, immediately before verification in CI.
Frequently Asked Questions
Does npm audit signatures check for known vulnerabilities too?
No. That is npm audit’s job, which checks package versions against the advisory database. npm audit signatures is a separate, unrelated command that verifies cryptographic registry signatures and Sigstore provenance attestations. Run both in CI; they catch different classes of supply-chain risk.
Why do most of my dependencies show zero provenance attestations?
Provenance adoption is voluntary and comparatively recent. A package only carries an attestation if its maintainer published it with npm 9.5+ and the --provenance flag from a supported CI environment. Registry signature verification, by contrast, applies to nearly all packages published since npm introduced package signing.
Can I require provenance for only my own organization’s packages?
Yes. Filter npm audit signatures output or use a custom script against npm ls --json combined with npm view <pkg> dist.attestations.url for packages matching your scope, then fail CI only when an internal package lacks an attestation, while tolerating third-party dependencies that do not yet provide one.
What does an invalid signature actually indicate, versus a missing one?
A missing signature means the package predates signing or its publisher never opted in — a lower-severity gap. An invalid signature means a signature exists but does not match the package’s content hash, which can indicate tampering, a registry mirror serving altered content, or a corrupted download, and should be treated as a hard failure.
Does this work with private registries or Verdaccio?
npm audit signatures only verifies packages resolved from the public npmjs.org registry keys and Sigstore’s public infrastructure. Private registries and proxies like Verdaccio that mirror packages generally pass signatures through unmodified, but self-hosted packages published internally without --provenance will show as unattested, which is expected.
Related
- npm Provenance & Sigstore Attestation — how the attestation this command checks is generated in the first place.
- Publishing with npm Provenance in GitHub Actions — the workflow-side configuration that determines whether your own packages will pass this exact check for downstream consumers.
- Validating Packages Before Publish — pre-publish checks that complement post-install verification by catching structural issues before a tarball is ever signed.