npm launched in 2010 and made installing someone else's code a single command. The cost moved from adding it to knowing what you had added.
npm made the cost of adding code asymmetric
When npm shipped in 2010, the friction that had always governed how much third-party code a JavaScript project carried was already gone in spirit — Isaac Z. Schlueter wrote the tool to formalise what the Node.js community was already doing informally, sharing modules by hand. What npm did was industrialise the habit. Installing someone else's code dropped from an afternoon of locating, downloading, and integrating a library to a single terminal command. The cost of adding a dependency fell close to zero. The cost of understanding what you had added did not move.
That asymmetry is the central fact of the npm era. It does not make npm a mistake; it makes it an honest exchange that took years for the industry to read the full terms of.
The asymmetry in plain terms
- Adding a dependencyone command, seconds, done
- Understanding a dependencyreading code you didn't write, written by someone unknown, possibly including their own unreviewed dependencies
- Auditing a treeongoing, structural, cannot be completed once and filed away
The registry and the graph it enabled
npm arrived alongside the CommonJS module format, which gave Node a way to express that one file depended on another. Once you could state a dependency in code, you could resolve it mechanically, and once you could resolve it mechanically, you could nest it: a package that depended on a package that depended on a package. The dependency graph is not a pathology npm introduced; it is the natural shape of modular software. npm just made it effortless to grow one.
Semantic versioning — the major.minor.patch convention — was the discipline meant to make that graph manageable. A patch increment should be safe. A major increment signals a breaking change. In theory, a project pinned to a range like ^1.4.0 could accept security fixes without accepting breakage. In practice, the convention is advisory: a maintainer who ships a breaking change in a patch release is violating a norm, not a law, and the registry has no mechanism to enforce it. Semantic versioning's specification spells out the intent clearly; the gap between intent and execution is where operational surprises live.
What grew on top of this arrangement was a registry of a scale nobody had planned for. npm became the largest software registry in the world by package count, a fact that is both a genuine achievement and a source of the audit problem. The more packages exist, the more paths exist from your package.json to code you have never read, written by people you have never heard of, whose continued involvement in the project is unknown.
Chronology
- 2010npm launches alongside Node.js's CommonJS module system
- March 2016left-pad unpublished; thousands of builds break; npm changes unpublish policy
- 2018
npm auditintroduced as a built-in vulnerability check - 2018event-stream incident: malicious code added via a handed-off maintainer
- 2020npm, Inc. acquired by GitHub (owned by Microsoft)
- January 2022colors.js and faker.js deliberately sabotaged by their maintainer
The left-pad moment as diagnostic
The arrangement's hidden assumptions became visible in March 2016. A package called left-pad — eleven lines of code that padded a string on the left — was unpublished from the registry by its author following a dispute, and thousands of builds across the ecosystem broke simultaneously. The failure was not a security incident and not a bug; it was a dependency being removed. The ecosystem had grown comfortable assuming the registry was permanent, because in practice it mostly was. Left-pad demonstrated that the comfort was not a guarantee.
What the incident diagnosed was not a flaw in npm specifically but a flaw in the mental model that cheap addition had encouraged. If adding a dependency costs one command, and your dependencies' dependencies are added transitively without deliberate choice, you can accumulate a tree of hundreds of packages without ever explicitly deciding that any of them should be in your project. The removal of eleven lines of code mattered because the lines were load-bearing in ways that nobody had mapped until they were gone.
npm responded by changing the unpublish policy, but the deeper question — what does it mean to depend on something you have not audited — remained open.
Supply chain as attack surface
The more consequential lesson came later and was darker. If the graph runs deep and is largely unexamined, it is also an attack surface. In 2018 the event-stream package was handed by its original maintainer to a new contributor, who added a malicious dependency targeting a specific Bitcoin wallet application. The package had millions of weekly downloads. The attack was in the transitive tree, not in any code the affected projects had written or chosen.
The event-stream incident and later episodes — including the 2022 deliberate sabotage of colors.js — made the term supply chain attack common in front-end conversations. The concept had existed in systems security for decades; npm made it newly relevant to JavaScript developers who had not previously thought of their dependency tree as a trust boundary. A supply chain, in this context, is every piece of code that runs in your build or in production that did not originate in your repository. On a mature front-end project, that can be a very large set.
npm audit, introduced in 2018, was the registry's built-in response: a command that checks your installed packages against a database of disclosed vulnerabilities. It surfaced real problems and also produced enough noise — advisories for vulnerabilities in development-only code paths, or in packages ten levels deep in a tree you cannot easily restructure — that teams learned to read the output selectively, which in turn created its own risks.
What the arrangement actually rests on
The npm registry in its current form is governed by npm, Inc., which was acquired by GitHub in 2020, which is itself owned by Microsoft. The packages in the registry are published by individuals and organisations who retain ownership of their code under whatever licence they choose. There is no formal review of a package before it is published; the speed that makes the registry useful is inseparable from the absence of gatekeeping that makes it risky.
The practical response the industry developed — lockfiles, private mirrors, dependency pinning, software composition analysis tools — are all attempts to re-introduce, at the project level, the deliberateness that the registry's frictionless model removed at the ecosystem level. They work, and they add overhead, and they require maintenance. None of them resolves the original asymmetry; they manage it.
Brendan Eich created JavaScript in 1995 as a scripting language for a web page. The dependency graph that an ordinary front-end project carries in 2025 would have been unrecognisable to that context: thousands of packages, transitive relationships several layers deep, maintained by the npm registry under corporate ownership, auditable in principle and in practice partially examined. npm did not cause this situation; it enabled it. The tool performed exactly as designed. The cost was always there — it just moved, from the moment you decided to add something to the years you spent understanding what you had.