Supply chain security map
Dependency Nebula
Your app is a few thousand lines of your code inside a nebula of other people’s.
Real dependency snapshot · hypothetical attack
- 40 packages you did not write
- 14 accounts can publish them
- 26 can be published by one account (A)
- 6 hops from your code to the deepest package
- 2 carry a provenance attestation
- 0 declare an install script
Snapshot of “npm install [email protected] --package-lock-only --ignore-scripts”, resolved on 23 September 2026 from the public npm registry. Nothing is fetched while you use this page.
quorate@1.3.0
- Your code
- Publishable by the attacker
Model boundaries
- The tree and its metadata are real: “npm install [email protected] --package-lock-only --ignore-scripts”, resolved on 23 September 2026. The attack is hypothetical; no package or account here has been compromised.
- Maintainer accounts other than mine are shown as letters, so this page names no one but me. The attack is hypothetical and says nothing about these maintainers’ security.
- A malicious release is the next patch version. By default it is built on each package’s latest release, as Microsoft describes for ChainDrop; 7 of A’s 26 packages here sit on an older line whose ranges refuse it. “Version in your tree” is the worst case: an attacker who patches every line in use. Caret (^) and tilde (~) ranges accept a patch on their own line.
- Every installed package is assumed to be loaded when the app runs, so import-time code always runs.
- min-release-age delays a new version: npm installs only versions available for more than the set number of days, so on install day 3 a 3-day window still holds a release (install days are counted from the same time of day as the release). It only protects you if the bad release is caught and removed within the window; detection is not simulated.
- A lockfile protects installs that honour it. npm update moves each package to the newest version its ranges allow, and without a lockfile npm install resolves every range afresh.
Read more
What I wrote about these incidents
- ChainDrop Rode a Patch Bump. Make Patch Bumps Wait.5 August 2026 · How the worm spread through patch releases, and the .npmrc baseline I proposed.
- The AsyncAPI npm Compromise: Provenance Proves the Pipeline, Not the Commit16 July 2026 · Why valid provenance and --ignore-scripts did not stop an import-time payload.
Primary sources
- Microsoft: ChainDrop supply chain compromise (4 August 2026) (opens in a new tab)Preinstall payload; the worm takes each package’s latest tarball, increments the patch version and republishes every package the stolen token can reach.
- Microsoft: the AsyncAPI npm compromise (15 July 2026) (opens in a new tab)Import-time payload that --ignore-scripts does not stop, published with valid provenance.
- GitHub changelog: npm install-time security (8 July 2026) (opens in a new tab)In npm 12, dependency preinstall, install and postinstall scripts no longer run unless allowed.
- npm docs: config (min-release-age, ignore-scripts, allow-scripts) (opens in a new tab)min-release-age installs only versions available for more than the given number of days.
- npm docs: npm ci (opens in a new tab)npm ci requires a lockfile, errors on a mismatch and never writes to it: installs are frozen.
- node-semver: caret and tilde ranges (opens in a new tab)^ allows changes that keep the left-most non-zero part; ~ allows patch changes.
- npm docs: package-lock.json (opens in a new tab)The lockfile maps every install location and flags packages with install scripts (hasInstallScript).
- npm docs: npm install (opens in a new tab)With a lockfile, the install is driven by it; without one, npm resolves the ranges in package.json.
- npm docs: npm update (opens in a new tab)npm update moves packages to the latest version their semver ranges allow.