The 2018 event-stream incident began with an ordinary handover of an unpaid maintenance burden, which is the part that has not been solved.

Empty chair facing away in a bare meeting room with half-shut blinds
A maintainer handed a popular package to a stranger The 2018 event-stream incident began with an ordinary handover of an unpaid maintenance burden, which is the part that has not been solved.

When the transfer looked like generosity, and the payload was already inside

In November 2018, security researchers discovered that a widely-used npm package called event-stream had been shipping malicious code for roughly two months. The payload was nested inside a dependency, obfuscated, and aimed at a specific target: the wallet software for a cryptocurrency called Copay, built by BitPay. The attack was narrow enough that most of the package's millions of weekly downloads were never at risk. The arrangement that made it possible, however, applied to almost everything.

event-stream had been written by Dominic Tarr, who maintained it for years without pay and eventually lost interest. In September 2018, a new GitHub account called right9ctrl opened a pull request to the repository, offered to help, and was handed full publish rights on npm. The transfer was not reckless by the norms of the ecosystem — it was normal. Maintainers routinely burned out, and handing off a package was considered a responsible exit. Tarr has said publicly that he had no reason to distrust the request; the new account had made small, plausible contributions first. The package was downloading in the millions per week. The new maintainer added a dependency called flatmap-stream, which contained the malicious payload, and published the result. Nobody noticed for two months.

Lifted from the piece

Key dates

  1. September 2018right9ctrl receives publish rights to event-stream; flatmap-stream added as dependency
  2. November 2018developer opens GitHub issue noting suspicious dependency; payload discovered, malicious versions removed
  3. 2016left-pad unpublishing exposes a different fragility in the registry
  4. January 2022colors.js and faker.js deliberately sabotaged by their maintainer

What the payload actually did

The attack was not opportunistic. flatmap-stream carried an encrypted payload that decrypted and executed only when it detected the presence of specific modules used by the Copay application. Outside that context, the code did nothing visible — which is part of why it sat undetected for weeks across an ecosystem that processes continuous automated installs. The target was private keys stored in Copay wallets, which the payload would attempt to exfiltrate. The narrowness of the target was a feature of the attack design, not a coincidence.

The discovery came via a GitHub issue opened by a developer who noticed that event-stream, a utility for working with Node.js streams, had no obvious reason to depend on flatmap-stream, which handled a different kind of data transformation. That question, asked publicly on the issue tracker, unravelled the rest. The npm security team was notified, the malicious versions were removed, and the Copay team issued a fix. The window of exposure lasted from around September to November 2018.

npm logo in white lowercase letters on a red rectangular background
A dependency became free to add and expensive to audit 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.Photo: Npm-logo · Wikimedia Commons

The arrangement that made it possible

event-stream's story is not primarily a story about malware sophistication. It is a story about what the npm registry was and what it assumed. npm, launched in 2010 by Isaac Z. Schlueter as a way to make sharing Node.js modules frictionless, had succeeded so completely that the ecosystem's health rested on a vast population of small packages maintained by individuals with no institutional backing, no contracts, and no obligation to continue. The number of direct dependents on event-stream ran into the thousands; the transitive reach was orders of magnitude larger.

The registry had no mechanism to verify that a transfer of publish rights was safe. It had no flag to indicate that a package had changed maintainers. Downstream consumers had no automated notification that the person publishing their dependency was no longer the person who had written it. Semantic versioning — the system by which version numbers are supposed to signal the magnitude of a change — said nothing about changes in authorship. A package that had been trustworthy for six years could become untrustworthy overnight, and nothing in the standard consumer workflow would signal that.

This was not a gap that had been overlooked so much as a gap the ecosystem had never had to confront at scale before. The dependency graph for a typical front-end project by 2018 ran to hundreds of packages, most of which had never been read by the people depending on them. The npm install command made the addition of a new dependency feel costless, and in terms of immediate effort it was. The cost was diffuse, deferred, and invisible — until it was not.

Lifted from the piece

The attack's logic

  • TargetCopay, the BitPay cryptocurrency wallet application
  • Vectora new dependency (flatmap-stream) containing an encrypted, conditionally-executing payload
  • Detection windowapproximately two months of live downloads before discovery
  • Trigger conditionpayload only executed when Copay-specific modules were present in the environment
One small screw lying alone on a vast pale floor, shot from standing height
Eleven lines, and thousands of builds In March 2016 an eleven-line package was unpublished and broke builds across the ecosystem. The code was trivial; the dependency graph was not.

What changed, and what did not

npm's parent company at the time, npm, Inc., responded by adding some tooling: two-factor authentication requirements for maintainers of high-impact packages, and eventually a mechanism called package provenance, introduced years later under GitHub's stewardship, which allows publishers to cryptographically link a published version to the source repository and the CI workflow that built it. These are genuine improvements. They raise the cost of a certain class of attack.

What they do not change is the underlying condition: the most-downloaded packages in the ecosystem are disproportionately maintained by individuals who are not compensated for that work and who will, eventually, want to stop. The right9ctrl account exploited not a software vulnerability but a social one — the reasonable desire of a tired maintainer to hand something off to someone willing to take it. TC39, the committee that stewards the JavaScript language standard, operates with institutional backing and a formal process. npm's package ecosystem does not have an equivalent. The registry is a public utility that was built by a company and runs on volunteer labour, and those two facts have never been fully reconciled.

The incident sits alongside the left-pad unpublishing of 2016 and the deliberate sabotage of colors.js and faker.js in early 2022 as a set of demonstrated failure modes — not theoretical risks but events with timestamps. Each exposed a different assumption baked into the arrangement: that a small package was safe to depend on broadly, that a maintainer would remain rational, that a transfer of ownership meant continuity of intent. None of those assumptions were unreasonable. None survived contact with a sufficiently motivated actor or a sufficiently burned-out human.

The next transfer of a popular package to a new maintainer proceeded, as they still do, on trust.

The event-stream incident is documented in the GitHub issue thread where it was uncovered — a public record of a real-time forensic process, readable in full, showing exactly how the discovery unfolded and what the community knew and when. What it does not show, because it cannot, is a solution. The thread closes with the malicious package removed and the repository archived. The next transfer of a popular package to a new maintainer proceeded, as they still do, on trust.