Run npm audit on a healthy project and it tells you about known vulnerabilities. Run npm install and it verifies a checksum and a registry signature. Both of those pass on almost every package on the registry, which is reassuring right up until you ask what they are actually promising.
We checked every package this site installs — 302 unique package-and-version pairs, resolved against the npm registry — for two separate things that are easy to confuse.
100% carry a registry signature. 10.8% of the ones that ship to users can prove where they came from.
Two different promises
A registry signature is npm signing the bytes it hands you. It means the tarball you downloaded is the tarball npm has, unmodified in transit. npm applies it to everything it serves, which is why our figure is 100% and why yours will be too. It is a transport guarantee.
A provenance attestation is a different claim: a signed statement linking this exact tarball back to the source commit it was built from and the CI job that built it. It answers "where did this code come from", which the signature does not even attempt.
The distinction matters because the attacks people worry about do not involve modifying a package in transit. They involve a compromised maintainer account, or a build machine that injected something the repository never contained. A registry signature is completely silent on both. Provenance is the thing that would catch them.
| Registry signature | Provenance attestation | |
|---|---|---|
| Production dependencies | 100% | 10.8% |
| Development-only dependencies | 100% | 57.9% |
The half that ships to users is the less verifiable half
That table contains the result we did not expect. Our development toolchain — the build tools, bundlers and linters that never leave a developer's machine — is 57.9% traceable. The production dependencies, the code that gets bundled and sent to every visitor, sit at 10.8%.
The inversion has a boring and probably correct explanation. Modern build tooling is young, maintained by funded teams, and published from CI pipelines that turn provenance on by default. Runtime libraries skew older: a date formatter or a text editor component written in 2019 by one person publishing from a laptop has no CI job to attest to, and adding one is work with no visible reward.
So the packages most likely to be able to prove their origins are the ones whose compromise would affect developers, and the packages least likely are the ones whose compromise would reach every reader of the site. That is exactly backwards from where the risk sits, and nobody chose it.
Our own good news is thinner than it looks
Nineteen production packages carry provenance. It would be easy to leave that there and sound reasonably healthy. But twelve of the nineteen are platform builds of a single package family — @napi-rs/canvas, which publishes a separate binary for every operating system and architecture it supports.
Counted as a decision rather than as a row in a lockfile, that is one maintainer's choice to publish with provenance, appearing twelve times. Strip it out and seven distinct packages in our production tree can prove their origins: a font package, d3-format, easymde, linkifyjs, opencc-js, pdfjs-dist, and prosemirror-tables.
Seven. Out of a hundred and seventy-six.
What a missing attestation does and does not mean
It would be dishonest to turn 157 packages without provenance into 157 things to worry about, so let us be precise about what the number is.
npm only shipped provenance in 2023. Most of the packages in any mature dependency tree were published before the feature existed, and a package that has not needed a release since then has had no opportunity to adopt it. Plenty of excellent, careful maintainers publish by hand from a machine they trust, which produces no attestation and no problem.
Absence of provenance is absence of evidence, not evidence of compromise. What it costs you is the ability to answer a question quickly when it matters. If a maintainer account is compromised tomorrow, the packages with provenance let you check, mechanically, whether the version you installed came from the repository you think it did. For the other 157 you would be reading changelogs and hoping.
Check your own tree
The registry will tell you directly. For any package and version:
npm view [email protected] dist.attestations
Output means there is a provenance attestation. No output means there is none — and note that npm view express dist.signatures will return something either way, which is precisely the confusion this guide exists to clear up.
If you publish packages yourself, the other side of this is one flag. Publishing from a supported CI provider with npm publish --provenance generates and uploads the attestation as part of the release. There is no key for you to manage; the CI provider's identity does the signing. It is close to the cheapest security improvement available to a package author, which is part of why the 10.8% is frustrating rather than tragic.
Our hash generator covers the adjacent habit of verifying a downloaded artefact against a published checksum, which is the manual version of the same idea.
The same question one layer up produced a similar answer: what you are trusting when you download a model found that 99% of the most-downloaded models on Hugging Face ship documentation and one in five declares what it was trained on. Different registry, same shape — the artefact is signed, the origin is not stated.
What this measurement is and is not
This is one project's dependency tree. It is not a survey of npm, and the specific percentages are ours rather than the ecosystem's — a project built entirely on 2025-era libraries would score far better, and one carrying a decade of accumulated dependencies would score worse.
We deliberately checked our own lockfile, not a list of the most popular packages. A top-100 list reflects the healthiest corner of the registry—well-funded packages with mature release pipelines. A real project's dependency tree includes everything those packages drag in. That’s what actually lands on your disk, and that's what you're trusting.
We also counted unique package-and-version pairs, not installed directories, so a library appearing at four different depths counts once. All 302 resolved; nothing was dropped for a network failure and then quietly excluded from the denominator.
The script is in our repository and points at any package-lock.json you give it. Figures are from 19 August 2026 — and the useful exercise is not reading ours but running it against yours, because the number that should worry you is the one for the code you ship.