event-stream - "Can I Maintain This?" โ Publish Rights Handed Over โ Encrypted Payload in a New Dependency โ Targeted Theft From One Bitcoin Wallet App
In September 2018 the maintainer of event-stream, an npm package doing roughly two million downloads a week that he had not actively maintained since 2012, transferred publish rights to a volunteer who asked for them. The volunteer, using the handle right9ctrl, added a new dependency called flatmap-stream. Inside it was an encrypted payload that decrypted itself using the package description as the key, then targeted exactly one thing: the build process of Copay, a Bitcoin wallet application. It harvested account data and private keys from wallets holding more than 100 BTC or 1,000 BCH. It was found on November 26, 2018, by a developer investigating a deprecation warning. This is the oldest chain on this page and the template for several of the newest. The handover mechanism reappears in XZ Utils six years later, and the maintainer's reply to the outrage - that he had given away a package he no longer used, for free, and owed nobody anything - remains the sharpest statement of the sustainability problem underneath all of it.
No credential was stolen and no system was exploited. Dominic Tarr had written event-stream years earlier, stopped using it in 2012, and continued receiving issues and requests for a package he had no personal stake in. Someone offered to help, and he did the thing the open-source ecosystem asks maintainers to do in that situation: he handed it over. The package still served around two million downloads a week, so the handover transferred write access to a substantial share of the JavaScript ecosystem on the strength of a friendly request. What makes this the template rather than a curiosity is that the same route was taken again, far more patiently, against XZ Utils in 2024 - and in that case sock puppets manufactured the pressure that made handover feel necessary.
event-stream, roughly 2 million downloads per weekMaintainer's position: Had not used it since 2012; unpaid, still fielding issues
Mechanism: Publish rights transferred on request to right9ctrl
Compromise involved: None
Repeated in: XZ Utils, 2024 - same route, with manufactured pressure added
flatmap-stream as a dependency - a package created for this purposeThe malicious code was never in event-stream itself. A new dependency was added, and that dependency carried the payload - which means anyone auditing event-stream's own source found nothing wrong, and the change that mattered was a single line in a manifest. Adding a dependency is among the most routine things a maintainer does and among the least scrutinized by consumers, most of whom never saw the change at all because it arrived transitively, several hops down a tree they had no reason to inspect. Legitimate later versions were also released, which further normalized the package's activity.
event-stream 3.3.6 adds flatmap-stream as a dependencyWhere the payload lived: In the new dependency, not in event-stream
Visible change: One line in a manifest
Consumer visibility: Effectively none - it arrived transitively
Auditing event-stream itself: Would have found nothing
The construction is elegant in a way that is worth appreciating for what it defeats. The malicious logic sat encrypted in a file dressed as a test fixture, and the decryption key was the description field of the package that would consume it - so the payload only decrypted successfully inside the intended victim's build, and anyone examining the code elsewhere saw an opaque blob that decrypted to nothing. That is targeting enforced by cryptography rather than by a conditional statement, and it makes analysis outside the target environment close to useless. Note how directly this prefigures XZ Utils, where the payload also hid in files presenting as binary test fixtures.
Decryption key: The description field of the consuming package
Effect: The payload only decrypts inside the intended victim's build
Analysis elsewhere: An opaque blob that decrypts to nothing
Same idea in 2024: XZ Utils hid its payload in binary test fixtures
Millions of installations, and the attacker wanted exactly one application. The code injected itself into Copay's build, checked balances, and exfiltrated account data and private keys only from wallets above roughly 100 BTC or 1,000 BCH - deliberately ignoring smaller holdings to reduce the chance of anyone noticing. Copay confirmed the malicious code shipped in versions 5.0.2 through 5.1.0. The strategic point is uncomfortable: precision targeting means the enormous population of collateral installs is not a bug in the attacker's plan but the camouflage for it, and it means "we use that package but we're not the target" is true right up until someone else's payload has a broader trigger.
Threshold: Wallets holding more than ~100 BTC or ~1,000 BCH
Taken: Account data and private keys, sent to an external server
Affected releases: Copay 5.0.2 through 5.1.0
Why ignore small wallets: Fewer victims noticing means longer dwell time
Eleven weeks passed between injection and discovery, and it ended because one person was curious about something minor. The thread that followed, titled "I don't know what to say.", became one of the more consequential documents in open-source security - not for the technical analysis but for Tarr's response to the anger directed at him. He pointed out that he had published the package for free, stopped using it years earlier, and was being blamed by people who had taken his work without payment and without contributing, for failing to keep guarding it indefinitely. That argument has not been answered since. It is the same condition XZ Utils exploited in 2024, and the reason maintainer funding shows up as a security control on this site rather than as a goodwill gesture.
Trigger for discovery: A developer investigating a deprecation warning
Venue: GitHub issue #116, "I don't know what to say."
The lasting argument: Free work, abandoned years earlier, guarded indefinitely by whom?
Same condition exploited in: XZ Utils, 2024
event-stream, since the original maintainer no longer had rights to fix itThere is a detail here that catches people out. When Tarr was told about the backdoor, he could not remove it - he had transferred publish rights, so he had no more authority over the package than any other user, and had to ask npm to intervene. npm removed flatmap-stream and the affected version and took ownership of event-stream itself, advising users to run npm audit and update. That is worth holding onto: transferring maintainership is irreversible from the transferring side, so the decision to hand a package over is effectively a decision to trust the recipient permanently, made at a moment when the maintainer is usually tired and relieved that someone volunteered.
flatmap-stream and the affected event-stream versionOwnership: npm assumed ownership of
event-streamWhy the original maintainer could not act: He had already transferred publish rights
Guidance: Run
npm audit and updateStructural point: Handover is irreversible from the transferring side
๐ก How to Defend Against This Chain
--ignore-scripts, and default-denying egress from build environments with a narrow allow-list, breaks both halves of that without requiring anyone to have spotted the dependency.event-stream was two million downloads a week of code its author had not needed since 2012. Periodically audit whether each dependency still earns its place, prefer the standard library or a small vendored function over a micro-package, and remember that removing a dependency is the only mitigation that is permanent.