debug Has 653M Weekly Downloads. One Publisher Hasn’t Touched It in 8 Years. They Can Still Push a Release.

We scanned top npm packages for dormant publishers with active publish access. Accounts inactive 5–8 years can still push code to packages installed 2.6 billion times per week.

npm audit checks for known vulnerabilities. It doesn’t check whether someone who hasn’t published in eight years can still push a release to a package your project installs every build.

We scanned the publisher lifecycle of top npm packages and flagged every account that (a) has current publish scope and (b) hasn’t published to that package in 12+ months. The findings cover 2.6 billion weekly downloads.


The data

Package Weekly Downloads Dormant Publisher(s) Last Published Inactive
ws 184M einaros, v1, 3rdeden Jun 2013–Jun 2016 120–156 months
debug 653M tootallnate Sep 2017 105 months
cliui 207M bcoe Aug 2020 70 months
has-flag 296M sindresorhus Jul 2021 59 months
tslib 389M typescript-bot Oct 2024 20 months
cross-spawn 234M satazor Nov 2024 19 months
escalade 168M lukeed Aug 2024 22 months
yargs 219M bcoe, oss-bot Apr/May 2025 14–15 months
semver 803M npm-cli-ops May 2025 13 months

Every one of these accounts can run npm publish right now and push code that lands in your node_modules/ within minutes.


Why this matters

The npm token model is straightforward: if you have publish access, you can publish. There’s no “inactive” state, no timeout, no re-authentication required after years of dormancy. A token from 2017 works in 2026.

That means every dormant account is a credential target. The attacker doesn’t need to find a zero-day. They need to find a .npmrc on an old laptop, a token in a GitHub Actions secret that was never rotated, or a phished email on an account the owner hasn’t checked in years.

This is exactly how the axios attack worked in March 2026. One stolen npm token. One push. 113 million installs per week compromised.


ws: three dormant accounts, one email domain

ws (WebSockets for Node.js) has four npm publishers. Only one—lpinca—has published in the last decade. The other three:

  • einaros (Einar Otto Stangvik, original creator): last published June 2013. 42 versions. 13 years of dormant access.
  • v1: last published January 2015. 13 versions. 11 years dormant.
  • 3rdeden (Arnout Kazemier): last published June 2016. 8 versions. 10 years dormant.

Two of these accounts—v1 and 3rdeden—list @3rd-Eden.com email addresses on npm. A single domain compromise could expose both accounts and give the attacker publish access to 184 million weekly downloads.

Revoking all three takes 30 seconds: npm owner rm einaros ws && npm owner rm v1 ws && npm owner rm 3rdeden ws. GitHub contributions are preserved—this only removes npm publish access.


debug: the 105-month case

debug has 4 historical publishers. Two had their access revoked: tjholowaychuk (TJ Holowaychuk, the original author) at 146 months inactive, and thebigredgeek at 109 months. The third, tootallnate (Nathan Rajlich), published 19 versions between 2014 and 2017. His last debug publish was September 2017. His access was never revoked.

The active maintainer, qix- (Josh Junon), published most recently. But tootallnate’s account has had publish access to 653 million weekly downloads for eight years without using it.

Someone cleaned up tjholowaychuk and thebigredgeek. They missed tootallnate.


What you can do

If you maintain a package:

  1. Audit who has publish access: npm access ls-collaborators <package>
  2. Revoke access for anyone who hasn’t published in 12+ months
  3. Enable npm Staged Publishing—it adds a review step before versions go live

If you depend on these packages:

  1. Pin versions in your lockfile and review lockfile diffs in PRs
  2. Run npx proof-of-commitment --file package-lock.json to check your full tree
  3. Add a CI gate: npx proof-of-commitment --fail-on=critical

How we found this

Commit tracks publisher lifecycle for every scored package: total historical publishers, who’s currently active, who has access but hasn’t published, and who was revoked. The dormant-with-access flag was added in v1.35.0.