Zennoxa Shield

Two years after npm shipped build provenance, 78% of a typical dependency tree still doesn't use it

We resolved the dependency tree of 34 of the most-used npm packages (484 packages) and measured two supply-chain signals against the public npm registry: under 1% run install scripts, but only 21.5% publish build provenance — 78.5% ship with no verifiable link from the package you install back to the source and CI that built it. Every number is reproducible from the published scripts.

August 25, 2026By Zennoxa Researchsupply-chainnpmprovenancedevsecopsappsec

TL;DR — We resolved the dependency tree of 34 of the most-used npm packages — 484 packages in total, the kind of set that lands in node_modules on a real install — and measured two supply-chain signals against the public npm registry.

  • Install scripts (preinstall/install/postinstall, the mechanism the 2025 Shai-Hulud and chalk/debug attacks used to run code): under 1% of the tree uses them (0.5% of the top tier, 0.8% across the full tree). The popular core has largely abandoned the classic run-code-on-install foot-gun.
  • Build provenance (npm's cryptographic "who built this, from which commit," shipped 2023): only 21.5% of that same tree publishes it. 78.5% ship with no provenance — no verifiable link from the package you install back to the source and CI that produced it.

The ecosystem removed one attack surface and skipped the verification layer that would detect the newer style of attack. Every number is reproducible from the scripts below.

Why these two signals

The high-profile 2025 npm compromises didn't exploit exotic bugs — they tampered with trusted packages. Two defenses matter:

  1. Fewer install scripts shrinks the "arbitrary code runs the moment you npm install" surface.
  2. Build provenance lets you verify a published package actually came from the source repo and CI it claims — the direct defense against a maintainer account or pipeline being hijacked to publish a poisoned build.

So: did the popular ecosystem shrink the first and adopt the second?

What we measured

  • Seed: 34 ubiquitous packages (react, vue, express, lodash, axios, next, webpack, vite, typescript, eslint, jest, chalk, debug, …).
  • Corpus: two lenses — top tier = seed + their direct dependencies (186 packages); full tree = bounded transitive closure over dependencies (484 packages).
  • For each: fetch the latest version's manifest from registry.npmjs.org, and check (a) scripts for any lifecycle install hook, (b) dist.attestations for build provenance.

Findings

1 — Install scripts are now rare in the popular tree.

Lens Packages With install script Share
Top tier (seed + direct) 186 1 0.5%
Full tree (transitive) 484 4 0.8%

The four that do (e.g. esbuild, unrs-resolver) are native-build packages — install scripts now concentrate around native compilation, not general-purpose packages. Your install-time code-execution risk scales with how many native modules you pull, not with raw dependency count.

2 — Build provenance is barely adopted.

Packages Share
Published WITH provenance 104 21.5%
No provenance 380 78.5%

Adoption is bimodal by publisher: whole families move together — the entire @babel/* set ships provenance, while most others ship none. Provenance is a CI/publisher decision, so it's all-or-nothing per maintainer, not a gradient.

What this means for your CI

  • You can't npm install your way out of install-script risk by counting dependencies — audit the native-build packages specifically; that's where the vector lives.
  • For the tampering class (hijacked publish), provenance is the check — but with ~78% of a normal tree lacking it, "require provenance" isn't yet enforceable across the board. Today it's a signal to prefer, not a gate you can flip on.
  • Both are cheap to check in CI: flag lifecycle scripts on install, and record which dependencies carry provenance so the number goes up over time.

Limitations (so you can weigh it)

  • Corpus skews popular/maintained — these are among the best-kept packages, so provenance adoption here (21.5%) is plausibly an upper bound; the long tail is likely lower.
  • By-name latest, not full semver resolution — a defensible census approximation, not a lockfile-exact tree. Transitive BFS is bounded; very deep tails are truncated.
  • Point-in-time — registry manifests change; the dataset is dated and re-runnable.
  • Presence of an install script is a vector, not proof of malice — most are legitimate native builds.

Reproduce it yourself

# needs only python3 + the public npm registry (no key)
python3 npm_install_scripts_census.py   # -> % of the tree with lifecycle install scripts
python3 npm_provenance_probe.py          # -> % published with dist.attestations (provenance)

The scripts and the results JSON are published with the study: npm_install_scripts_census.py, npm_provenance_probe.py, install-scripts results, provenance results. Change the seed, re-run, check the numbers. If a figure looks off, verify it against registry.npmjs.org/<pkg> directly.

FAQ

Does "no provenance" mean a package is unsafe? No — it means you can't cryptographically verify it was built from the source/CI it claims. It's a missing assurance, not evidence of compromise.

Are install scripts bad? They're a legitimate mechanism (native builds need them) and also the exact vector recent attacks used. The point is to know which dependencies use them.

How do I check my own project? Resolve your lockfile and run the same two checks against each package's registry manifest — the scripts above are the starting point.

Sources

npm registry (registry.npmjs.org, public). Provenance = npm build provenance / Sigstore attestations (public spec). Install-lifecycle scripts = npm docs. All figures recomputed 2026-08-25; corpus and scripts published for reproduction.

See what Shield finds in your code

Free to start, no credit card. One pass covers SAST, secrets, vulnerable dependencies, containers and IaC — ranked by what’s actually reachable, not just severity.

Scan your repo free

Prefer the terminal? The offline CLI runs with no account

More research
Two years after npm shipped build provenance, 78% of a typical dependency tree still doesn't use it — Zennoxa Shield