All vulnerabilities
CRITICALSupply chainexploited in the wildcurated

NPM-EVENT-STREAM-2018

npm · event-stream, flatmap-stream

Summary

The event-stream incident, disclosed in November 2018, was the wake-up call for npm supply-chain security, and a preview of the xz backdoor six years early. event-stream was a popular but unglamorous Node.js utility with around two million weekly downloads and a maintainer who had lost interest. When a stranger offered to take it over, the maintainer simply handed it off, no vetting, as open-source volunteers exhausted by unpaid work often do. The new "maintainer" then, patiently, slipped in a malicious dependency that was surgically targeted: it did nothing on almost every machine, activating only inside the build of a specific Bitcoin wallet app, where it tried to steal the private keys of anyone holding a large balance. It is the original lesson that a dependency is a person you are trusting, and that a maintainer handoff is a supply-chain attack surface.

How it happened

event-stream's original author, Dominic Tarr, had long since moved on; the package still had around two million weekly downloads but no active steward. A GitHub user going by "right9ctrl" emailed offering to maintain it, and Tarr, like many burnt-out maintainers, granted publish rights to a willing volunteer. ("He emailed me and said he wanted to maintain the module, so I gave it to him," Tarr later wrote. "I don't get anything from maintaining this module, and I don't even use it anymore.") It was a well-meaning, ordinary act, and it handed control of a hugely popular package to an attacker.

The takeover was patient, foreshadowing xz. In version 3.3.6 (published 9 September 2018) the new maintainer added a new dependency, flatmap-stream, that was still completely benign, building trust. Almost a month later, on 5 October, they published a poisoned flatmap-stream@0.1.1 carrying the payload, without touching event-stream itself, so the popular package became dangerous retroactively. The concealment was the clever part: the malicious code existed only in the minified package published to npm, not in the GitHub source, so anyone reviewing the repository saw nothing. It was also encrypted, using the description of its intended victim package ("A Secure Bitcoin Wallet") as the decryption key, so it stayed inert everywhere except where it was meant to fire. That target was Copay, a Bitcoin wallet built by BitPay. Inside a Copay release build, the backdoor decrypted, modified the bundled wallet code, and harvested wallet private keys and recovery seeds for accounts holding more than 100 BTC or 1000 BCH, sending them to an attacker's server. Copay versions 5.0.2 through 5.1.0 shipped it.

The damage

Because the targeting was so narrow, the confirmed direct theft was limited, and Copay patched quickly once it was found. The damage was conceptual, and enormous. event-stream proved that a trusted, ubiquitous dependency could be weaponised for nothing more than a friendly offer to help, that the payload could be hidden from anyone reading the source, and that it could be aimed with a sniper's precision at a single downstream application. npm removed the malicious packages and took ownership of event-stream itself to stop further abuse, and the episode pushed npm and the ecosystem toward two-factor authentication, audit tooling, and a general loss of innocence about npm install.

Why event-stream still matters

event-stream is the prototype for the entire modern supply-chain playbook. Social-engineering a maintainer handoff (later perfected in the xz backdoor). Adding a dependency while it is still clean and weaponising it weeks later, so the trust is earned before the betrayal. Hiding the malware in the published artifact rather than the source, so review misses it. Surgically targeting one downstream victim. Every one of those techniques is now standard. The lessons it teaches are still the right ones: a dependency is a trust relationship with whoever controls it, so treat ownership changes as security events; pin and review dependency updates, especially new transitive packages that appear quietly; build from reviewed source and compare against what is actually published; and keep your dependency tree small, because every package is one more overworked, unpaid maintainer you are implicitly trusting.

How to fix it

  • Remove the malicious version (specifically `flatmap-stream@0.1.1`, and event-stream 3.3.6 which pulls it in), pin to a known-good release, and rebuild and republish anything that bundled it.
  • For affected apps like the Copay wallet, treat exposed private keys and funds as compromised and move funds to new wallets generated on a clean system.
  • Audit what the dependency could reach, and rotate any secrets handled by code that ran alongside it.

How to avoid it

  • Treat a maintainer or ownership change on a dependency as a security event worth reviewing, not a routine update.
  • Pin dependencies and review updates, especially a new transitive dependency a package suddenly pulls in; flatmap-stream arrived exactly that quietly, then turned malicious weeks later.
  • Build from reviewed source where you can and compare it against the published artifact; the payload lived only in the npm tarball, not the GitHub repo.
  • Reduce dependency sprawl: every package you add is a maintainer you are trusting, often an unpaid and overworked one, so keep the tree small and watch the ones that touch anything sensitive.

References

Related vulnerabilities

All Supply chain →