Kevin Roleke

[email protected]

github.com/kevinroleke

npm: How did we get here?

Published 2025-09-02

As of July 31, 2025 there are... let's see...

npm i all-the-package-names
node -e 'console.log(require("all-the-package-names").length)'
3532585

3,532,585 packages published in the npmjs registry. This includes the all-the-package-names package and its four dependencies, is-number, tap-spec, tape and all-the-package-repos.

Does that seem a little ridiculous that such a simple library, a list of all npm packages, has four dependencies? The answer to this rhetorical question becomes far more clear when you consider the dependencies of those dependencies.

npm i --include=dev # 3 "critical vulnerabilities" detected btw
ls node_modules
all-the-package-repos        es-define-property        is-arguments             minimist                    side-channel-list
ansi-regex                   es-errors                 isarray                  mock-property               side-channel-map
ansi-styles                  es-get-iterator           is-array-buffer          object-assign               side-channel-weakmap
array-buffer-byte-length     es-object-atoms           is-async-function        object.assign               split
arraybuffer.prototype.slice  es-set-tostringtag        is-bigint                object-inspect              stop-iteration-iterator
array.prototype.every        es-to-primitive           is-boolean-object        object-is                   string_decoder
async-function               figures                   is-callable              object-keys                 string.prototype.trim
available-typed-arrays       for-each                  is-core-module           once                        string.prototype.trimend
balanced-match               fs.realpath               is-data-view             own-keys                    string.prototype.trimstart
brace-expansion              function-bind             is-date-object           parse-ms                    strip-ansi
buffer-shims                 function.prototype.name   is-finalizationregistry  path-is-absolute            supports-color
call-bind                    functions-have-names      is-finite                path-parse                  supports-preserve-symlinks-flag
call-bind-apply-helpers      get-intrinsic             is-generator-function    plur                        tape
call-bound                   get-package-type          is-map                   possible-typed-array-names  tap-out
chalk                        get-proto                 is-negative-zero         pretty-ms                   tap-spec
concat-map                   get-symbol-description    is-number                process-nextick-args        through
core-util-is                 glob                      is-number-object         readable-stream             through2
count-array-values           globalthis                is-regex                 re-emitter                  trim
data-view-buffer             gopd                      is-set                   reflect.getprototypeof      typed-array-buffer
data-view-byte-length        has-ansi                  is-shared-array-buffer   regexp.prototype.flags      typed-array-byte-length
data-view-byte-offset        has-bigints               is-string                repeat-string               typed-array-byte-offset
deep-equal                   has-dynamic-import        is-symbol                resolve                     typed-array-length
defined                      hasown                    is-typed-array           safe-array-concat           unbox-primitive
define-data-property         has-property-descriptors  is-weakmap               safe-buffer                 util-deprecate
define-properties            has-proto                 is-weakref               safe-push-apply             which-boxed-primitive
dotignore                    has-symbols               is-weakset               safe-regex-test             which-builtin-type
dunder-proto                 has-tostringtag           @ljharb                  set-function-length         which-collection
duplexer                     inflight                  lodash                   set-function-name           which-typed-array
es-abstract                  inherits                  math-intrinsics          set-proto                   wrappy
escape-string-regexp         internal-slot             minimatch                side-channel                xtend

Wtf! That's one-hundred-and-fifty packages!2 To be fair, these are developer dependencies which are only required for contributors to develop, build and release the all-the-package-names package; regardless, this is clearly an issue.

These packages are authored by 19 unique developers (in addition to 5 unattributed packages). Do you really trust the author of the isarray package not to run malicious code on your computer? How about the author of the escape-string-regex package? Do you trust them not to sell the package to a malicious actor, or to get phished?

The answer to these questions for the vast majority of JavaScript developers should be no; not because the authors of the aforementioned packages are necessarily malicious (they are likely not), but because you simply don't know how trustworthy these packages are until you investigate. And let's be honest... you're not investigating.

A BRIEF HISTORY OF PACKAGE MANAGEMENT

In the beginning there was UNIX. Any program that wasn't included with the operating system would have to be installed by downloading and extracting an archive, then running a ./configure script and finally make. This got annoying real fast.

Then came DPKG, likely the oldest package manager still in use today. When it was released by The Debian Project in 1993, DPKG had no ability to deal with dependency resolution or remote repositories. These shortcomings would later be addressed with the release of APT in 1998. Today when we think of a package manager, we think of something like APT, which can resolve and fetch dependencies from a remote repository.

While DPKG and other package managers for operating system were being developed, the first large scale ecosystem-specific code repositories started coming online, notably CTAN for TeX and later CPAN for Perl (both are still in use today).

CPAN is really the first thing that resembles npm. It has one community-open unified repository (for the most part) and a command line utility for searching, downloading and publishing packages.

An early attempt at creating a package manager for JavaScript was JSAN, of course, based on CPAN. JSAN was released four years before NodeJS and the advent of JavaScript for the backend, which is possibly why it never picked up any real steam.

This finally brings us around to npm, which was released in January 2010 to serve as the package manager and registry for the newly released NodeJS.

HOW DID WE GET HERE?

THE BEGINNINGS

When Isaac Schlueter pushed the first commit to the npm git repository, there was no registry as we know it today. Packages came from a JSON file in this git repo, which has sadly been lost to time.

This system was quickly replaced with a CouchDB app. At the time, anyone could publish to the registry without authentication and override anyone else's packages. The current iteration of the npm registry is a more secure and distributed version of this original CouchDB system.

FIRST PACKAGE

Contrary to the answer provided by the little AI box when you type "what was the first npm package?" into Google, the first package published was actually abbrev, not npm itself.1

PACKAGES OVER TIME

Due to changes to the npm registry over the last decade-and-a-half, it's hard to get exact data on the historical package count. These numbers are rough estimates and have been compiled from various blog posts, tweets and git commits.

The npm registry is really big3,4. This makes some sense, being that JavaScript is, much to my chagrin, the most used programming language.

SO WHAT?

It's tempting to scoff at this number and conclude that package count alone is indicative of something bad. First, let's put it into perspective against some other popular software registries:

We can clearly see that npm is more or less on par with these other registries in terms of raw package count, even being completely eclipsed by Maven Central. So why does npm have such a terrible security reputation? Let's look at transitive dependencies.

These are the median number of transitive dependencies for a few popular registries, calculated by GitHub in 2020:5

GitHub didn't have any data for transitive Java dependencies in the Maven Central repository, so I wrote a script to analyze 10,000 random packages (in 2025):

This is our first glaring issue.

WHY SO MANY DEPENDENCIES?

Ultimately, this comes down to two things: the standard library and developer culture.

Compared to languages like Java or Go, Node has a tiny standard library. In the last decade or so, we've seen massive improvement on this first problem with the addition of native FS, buffers, async hooks, fetch, etc. However, we are left over with the rot of obsolete packages like lodash, node-fetch, request, bluebird, and moment, which were previously very useful.

The main problem though, is micropackaging, a philosophy that has completely taken over the JavaScript ecosystem. Thousands of packages on the registry, each with millions of downloads per week consist solely of one liner functions. Rather than writing a quick utility function, JavaScript developers prefer to include a third party package.

Additionally, thousands of spam packages can be attributed to perverse incentives from the Tea protocol and web developers attempting to pad their digital resume with claims of maintaining several popular packages.

THE CONSEQUENCES

The supply chain threat is not just theoretical; it's absolutely epidemic to the JavaScript ecosystem. Personally, I opt to do any NodeJS development in a virtual machine.

There are countless examples of hacked package maintainers, packages being sold to malicious actors, and even malware being placed by the legitimate author. Everything from "protestware" to crypto miners to multi platform RATs are sneaking into your machine via npm install. The smart malware will even steal your registry token from .npmrc to infect your packages.

Let's take a look at some famous examples.

In July of 2025, a clever hacker noticed that npmjs.org had no SPF record configured. They proceeded to take advantage of this with a phishing campaign targeting maintainers of some popular packages. The packages eslint-config-prettier, eslint-plugin-prettier, synckit, @pkgr/core, napi-postinstall, got-fetch, and is, with a collective weekly download count of 96,063,394 were promptly compromised with malware targeting Windows.8

One particularly infuriating case is the Peacenotwar "protestware." This malware was planted by the real maintainer of the node-ipc package, and it's goal was to wipe the computers of anyone with an IP address in Belarus or Russia. A good reminder that people are not their governments, and that IP geolocation is not accurate. Also, believe it or not, this guy faced zero consequences and is still the maintainer of node-ipc and various other packages.7

The npm supply chain tactic also seems to be a favorite among hackers linked to the DPRK.9 In 2023, the Lazarus APT compromised connect-kit, a package used for connecting JavaScript dApps to Ledger cryptocurrency hardware wallets. Despite the malicious code only being live for 5 hours, they managed to swipe over $600,000 worth of cryptocurrency from unsuspecting users.10

There are so... so many more examples of compromised npm packages. I invite you to just google "npm supply chain" and see how many results pop up.

WHAT CAN BE DONE?

The registry has already implemented mandatory MFA for package maintainers, but that's not cutting it. I suggest that npm implements the following changes:

And perhaps more importantly, developers need to stop being lazy. You don't need a package to check if a number is even!

It's past time to stop carelessly running npm install.

SOURCES

  1. https://blog.izs.me/2017/02/my-first-npm-publish/
  2. https://github.com/nice-registry/all-the-package-names
  3. https://blog.npmjs.org/post/615388323067854848/so-long-and-thanks-for-all-the-packages.html
  4. https://nodesource.com/blog/npm-is-massive
  5. https://octoverse.github.com/2020/static/github-octoverse-2020-security-report.pdf
  6. https://blog.npmjs.org/post/612764866888007680/next-phase-montage
  7. https://en.wikipedia.org/wiki/Peacenotwar
  8. https://socket.dev/blog/npm-is-package-hijacked-in-expanding-supply-chain-attack
  9. https://socket.dev/blog/north-korean-contagious-interview-campaign-drops-35-new-malicious-npm-packages
  10. https://www.nodejs-security.com/blog/north-korea-malware-on-npm-and-ledger-connect-kit-crypto-heist