Copay event-stream / flatmap-stream Transitive-Dependency Backdoor (2018-11-26)
- Name: Copay event-stream / flatmap-stream transitive-dependency backdoor
- Date: 2018-11-26
- Protocol: Copay / BitPay Wallet build pipeline
- Chain: Bitcoin + Bitcoin Cash / npm supply chain
- Category: software supply-chain compromise / transitive dependency compromise / build-artifact substitution / private-key exfiltration
- Loss: publicly unquantified; the implanted payload specifically targeted Copay wallets holding more than
100 BTCor1000 BCH, and BitPay told affected users to assume private keys were compromised and migrate funds to brand-new wallets - Summary: The attacker did not compromise Bitcoin itself or BitPay’s production signing keys. Instead, they social-engineered their way into maintainership of the widely used
event-streamnpm package, added dependencyflatmap-stream, then published maliciousflatmap-stream@0.1.1. The payload was engineered to stay quiet almost everywhere else and only fully unwrap inside Copay’s release-build environment. During those builds it used Copay’s package description string"A Secure Bitcoin Wallet"as a decryption key, wrote a final-stage implant into@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js, and shipped that malicious code in public Copay releases5.0.2through5.1.0. On victim devices the implanted wallet code checked balances, harvested private keys and account data from high-value BTC/BCH wallets, and exfiltrated them to attacker infrastructure. - Trigger / entry point:
- The attacker first social-engineered maintainership over dormant but heavily reused npm package
event-stream. - On
2018-09-09,event-stream@3.3.6added direct dependencyflatmap-stream. - On
2018-10-05, maliciousflatmap-stream@0.1.1was published. - Copay’s dependency resolution pulled the poisoned chain into its release process, and public Copay versions
5.0.2through5.1.0were built with the implanted code. - The malicious code was intentionally target-aware: most
event-streamconsumers would execute nothing obviously harmful because the deeper stages only decrypted and activated in the Copay build environment.
- The attacker first social-engineered maintainership over dormant but heavily reused npm package
- Root cause:
- The decisive failure was an upstream software-supply-chain compromise, not an onchain protocol bug.
event-streammaintainership and npm publishing rights were socially captured, then used to introduce a malicious transitive dependency into a trusted package many projects consumed.- Copay’s release pipeline trusted the dependency graph deeply enough that attacker-controlled code could run during build and rewrite bundled wallet artifacts before publication.
- The preserved invariant is
a shared build dependency must not be able to silently become wallet-implant code inside released binaries. Once that failed, Copay’s real domains and real wallet UI no longer mattered because the shipped application itself had already been poisoned.
- Exploit path:
- The attacker convinced
event-streammaintainer Dominic Tarr to hand over meaningful project and npm publishing access to accountright9ctrl. - Benign-looking maintenance commits and the new
flatmap-streamfeature were added, culminating inevent-stream@3.3.6depending onflatmap-stream. - Weeks later, the attacker published malicious
flatmap-stream@0.1.1, so downstream installs of vulnerableevent-streamversion ranges would fetch the poisoned transitive dependency. - The malicious code read encrypted blobs disguised as test data and used npm-provided environment variable
npm_package_descriptionto decide whether it was inside the intended target. - Copay’s package description
"A Secure Bitcoin Wallet"acted as the decryption key that unlocked the deeper payload stages. - During Copay release builds for mobile / desktop targets, the malware rewrote
./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js, ensuring the final bundled wallet app carried attacker code to end users. - On affected user devices, the implanted code detected Cordova / Electron-style environments, inspected BTC/BCH wallet balances, and if balances exceeded
100 BTCor1000 BCH, harvested account data and private keys. - The harvested material was exfiltrated to attacker infrastructure at
111.90.151.134, turning a transitive dependency compromise into direct wallet-key theft.
- The attacker convinced
- What the attacker needed to be true:
- A trusted but lightly maintained upstream package had to be willing to grant new maintainership and npm publishing authority.
- Downstream applications such as Copay had to accept transitive dependency drift without pinning every artifact that influenced release builds.
- Copay’s build environment had to expose a recognizable secret or fingerprint — here, package description
"A Secure Bitcoin Wallet"plus known build commands — so the malware could stay dormant for everyone else and activate only for the intended wallet. - Build-time code execution had to be strong enough to rewrite a dependency file that would later ship inside the final wallet application.
- Users had to run the poisoned released wallet versions before warnings spread.
- Technical anatomy worth remembering:
- This was not just
malicious npm packagein the abstract. It was a surgically targeted transitive-dependency attack that used a broadly trusted package as camouflage until it reached one wallet’s release pipeline. - The first-stage logic hid encrypted data in a faux test fixture, decrypted additional stages in memory, and only wrote the final implant when the surrounding npm metadata matched Copay.
- The attack preserved a distinct cousin of later incidents like Ledger Connect Kit and Solana web3.js: the attacker did not mainly poison live browser prompts or exfiltrate keys from backend bots; they turned a victim’s own release build into the malware-delivery mechanism.
- The implanted application code targeted high-balance wallets specifically, which is why public loss accounting stayed murky even though the incident was severe enough for BitPay to tell users to assume key compromise.
- The release-response lesson is unusually sharp here: even a clean upstream publish after discovery would not retroactively clean already built wallet binaries or invalidate keys already harvested from installed clients.
- This was not just
- Representative artifacts:
- Compromised upstream package:
event-stream - Malicious dependency chain:
event-stream@3.3.6→flatmap-stream@0.1.1 - Affected public wallet versions:
5.0.2through5.1.0 - Fixed wallet version:
5.2.0 - Decryption selector / target fingerprint: npm package description
"A Secure Bitcoin Wallet" - Build-time overwrite target:
./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js - Publicly cited exfiltration endpoint:
111.90.151.134
- Compromised upstream package:
- Response / mitigation:
- npm removed
flatmap-streamandevent-stream@3.3.6from the registry after the compromise was reported publicly. - BitPay warned users not to run or open Copay versions
5.0.2through5.1.0. - BitPay released Copay
5.2.0and told affected users not to migrate by simply importing old seed phrases, but instead to update and send all funds to brand-new wallets because private keys may already have been exposed. - In the next hardening pass BitPay said Copay / BitPay
v5.3.1locked the dependency tree more aggressively and restricted wallet network connections so a future implanted client would have a narrower path to both ship and exfiltrate data. - The hard recovery problem was broader than
remove the bad npm package: BitPay needed a clean release built from a clean dependency graph, users needed to stop running already built poisoned binaries, and any keys the implant had already exfiltrated had to be treated as burned. - The incident became a canonical reminder that package rollback alone is not enough when poisoned dependencies have already influenced shipped wallet binaries or leaked keys.
- npm removed
Pattern tags
- software-supply-chain-compromise
- release-provenance-failure
- Shared upstream package identity stopped proving safe downstream release bytes: Copay’s own repository and product name stayed real while attacker-controlled transitive dependency code rewrote the wallet binary during build
- Dormant-by-default targeted payload activation: the backdoor stayed quiet for most
event-streamconsumers and only fully unwrapped when npm metadata and build flow matched Copay’s release environment - Build-time code execution became end-user key theft: the attacker did not need runtime wallet prompts or server compromise once the release pipeline itself could implant malicious logic into shipped client code
- Recovery required wallet-authority migration, not just package rollback: removing
flatmap-streamfrom npm did not help users who had already installed poisoned Copay builds or had already leaked keys
Why the exploit worked
- Copay trusted a transitive npm dependency chain deeply enough that a socially captured upstream maintainer could become release-artifact authority without ever touching Copay’s own top-level repository. The exploit lived in the gap between
reviewed wallet codeandall code allowed to participate in the build. - The malware was intentionally target-aware. npm’s own analysis said the payload did little in most environments, then used Copay’s package description string as a decryption key and release-build fingerprint. That let the attacker hide inside a broadly trusted package ecosystem without immediately detonating for every downstream consumer.
- Build-time code execution was enough to become wallet-distribution authority. Once
flatmap-streamcould rewriteReedSolomonDecoder.jsbefore bundling, BitPay’s real site, branding, and later package cleanup no longer mattered for users who installed the already poisoned wallet binaries. - The monetization rail was raw key exfiltration from end-user wallet software, not merely future transaction manipulation. BitPay’s own recovery guidance correctly treated affected seeds and private keys as burned authority that had to be migrated into brand-new wallets.
- The incident also showed that
unpublish the bad packageis not a full remediation story for release-pipeline compromises. The dangerous object was the already shipped Copay binary, not only the still-live npm tarball.
Three release-and-wallet-authority promotions this incident collapsed
-
A familiar shared dependency path still existed at all
- Copay did not start from an obviously rogue package name. The build still pulled a real, widely trusted dependency chain:
event-streamplus its new direct dependencyflatmap-stream. - The weak first fact was only
this is still a familiar npm package path present in our build, notthis path still resolves to the exact artifact lineage our wallet release review meant to trust.
- Copay did not start from an obviously rogue package name. The build still pulled a real, widely trusted dependency chain:
-
That familiar dependency path was treated as if it still proved Copay’s reviewed release lineage
- Once
event-stream@3.3.6depended on maliciousflatmap-stream@0.1.1, the attacker could use Copay’s package-description string"A Secure Bitcoin Wallet"as a target selector, decrypt later payload stages, and rewrite@zxing/library/.../ReedSolomonDecoder.jsduring the release build. - The dangerous jump was from
the build still uses canonical package namestothe bytes entering the shipped Copay artifact still match the reviewed release object, even though transitive dependency publication had already changed what the build executed.
- Once
-
That silently drifted lineage still kept full wallet-distribution and key-observation authority
- Public Copay releases
5.0.2through5.1.0inherited the implanted code as if they were still ordinary wallet releases, and installed clients then retained enough runtime authority to inspect balances, harvest high-value BTC/BCH private keys, and exfiltrate them. - BitPay’s recovery guidance matters because it preserved the final promotion clearly: once poisoned release bytes have already shipped,
publish a clean packageis not enough. Users need a provably clean rebuilt wallet, and already exposed keys have to be treated as burned authority and migrated into brand-new wallets.
- Public Copay releases
Detection and audit pivots
- Inventory every package with enough install-time or build-time execution to rewrite shipped artifacts, especially dependencies that enter bundling, code generation, packaging, or mobile / desktop release steps rather than only local development tooling.
- Treat maintainer churn and sudden dependency-graph growth in dormant but high-fanout packages as production-security events.
event-streamdid not need to look malicious at first; it only needed one new maintainer and one plausibly useful dependency edge. - Diff built wallet artifacts and bundled dependency files across releases, not just top-level source repos. In this incident the decisive mutation landed in
node_modules/@zxing/library/.../ReedSolomonDecoder.js, not in an obviously suspicious Copay source file. - Ask what build-environment metadata, package strings, signing config, or release commands could act as target selectors for malware trying to stay dormant everywhere else. Copay’s package description became a decryption key, which means seemingly harmless build metadata was already part of the attack surface.
- Constrain runtime egress for wallet binaries and release-test them under hostile assumptions. BitPay’s later network-connection restrictions are worth preserving because the implant still needed an exfiltration path after it reached user devices.
- During response, separate
registry cleanup,artifact lineage proof, andauthority rotation. For build-pipeline compromises, teams need to prove which exact versions executed, which exact rebuilt artifact is now safe, and which user or service keys must be treated as irreversibly exposed.
Failure-prevention checklist
- Treat build-participating dependencies as signer-critical or wallet-critical authority, not as mere developer convenience. Anything allowed to run during bundling, packaging, or release generation should inherit review and provenance rules closer to wallet code than to casual tooling.
- Lock dependency graphs and release inputs tightly enough that maintainer churn or transitive-version drift cannot silently change shipped wallet bytes between review and release.
- Produce and retain exact artifact-lineage evidence for each public wallet build: reviewed source commit, resolved dependency graph, lockfile state, build environment, and the exact shipped binary or package hash.
- Diff and attest bundled artifacts, not only repository sources. A healthy pipeline should catch
node_modulesor generated-file rewrites before they become signed mobile / desktop releases. - Minimize and audit build-environment secrets, package metadata, and release-only selectors that could let malware stay dormant for everyone except one high-value target product.
- Restrict default network egress from wallet clients and release-test exfiltration paths. Even if a poisoned build ships, the client should not have unconstrained freedom to talk to arbitrary collection infrastructure.
- Plan the response around authority rotation, not only dependency cleanup. If a wallet build may have exposed seeds or private keys, the safe recovery action is new-wallet migration, not
reinstall the app and keep the same secrets.
Audit prompts
- Which dependencies can execute code or rewrite files during install, build, bundling, packaging, signing, or release publication, and which of those currently escape the review rigor applied to first-party wallet code?
- Can the team prove, for one released wallet version, the full path from source commit and lockfile to the exact binary or package users installed, or does the release process still contain opaque steps where attacker-shaped bytes could enter unnoticed?
- What maintainer-transfer, package-takeover, or transitive-dependency-growth signals would currently trigger a production incident review before the next release goes out?
- Which build-time strings, environment variables, signing configs, or platform-specific steps could act as target selectors for malware trying to stay quiet in every environment except the intended victim’s release pipeline?
- If a dependency compromise were discovered today, could the team answer three separate questions quickly: which versions were built from poisoned inputs, which rebuilt artifact is now provably clean, and which user or service keys must be treated as burned authority?
- Are runtime network policies, telemetry review, and release-test environments strong enough to make post-install key exfiltration materially harder even when a poisoned build slips through?
Lessons that should already be standard
Reviewed repositoryis not the same thing assafe shipped wallet. If dependencies can execute during build, the trust boundary already includes the full release graph and its build environment.- Package cleanup is not artifact cleanup. Removing a malicious npm package from the registry does nothing for already built wallet binaries sitting on user devices.
- Artifact rollback is not authority rollback. Once a poisoned wallet may have exposed keys, seed phrases, or other signing material, those secrets should be treated as burned even after a clean rebuild exists.
- Dormant-by-default supply-chain malware is especially dangerous in crypto because the attacker only needs one release pipeline or one wallet family to unwrap into a high-value theft path.
- Wallet clients should assume that some upstream control failure will eventually happen and still narrow the blast radius with artifact attestation, constrained egress, and explicit key-migration playbooks.
Why this incident matters
- It is a clean preserved exemplar of the transitive-dependency / build-artifact-substitution branch of crypto supply-chain risk.
- It shows that the critical trust boundary in wallet software can sit upstream of the wallet repository itself, inside dependencies allowed to execute during release builds.
- It broadens the corpus beyond
real site, malicious promptandbackend SDK stole hot keysinto a third important cousin:the released wallet app was itself compiled from attacker-shaped dependency code. - It is also one of the sharpest early reminders that release recovery has two separate jobs: prove the rebuilt artifact lineage is clean, and assume previously exposed wallet authority material must be migrated rather than merely re-downloaded.
Internal linkages
- Core recurring pattern: software-supply-chain-compromise.
- Sharper release-integrity lens: release-provenance-failure — Copay preserved the build-pipeline version of
reviewed source was not the same thing as shipped artifact, because the malicious dependency rewrote wallet bytes during release rather than at page-load time. - Positive-design companion: exact-production-artifact-attestation-and-rollback-discipline — the recovery-side lesson is not only
remove the malicious package, butprove the rebuilt wallet artifact is the exact one users now execute and treat already exposed keys as compromised authority. - Useful contrast: ledger-connect-kit-npm-supply-chain-wallet-drainer-2023-12-14 — Ledger preserved the browser-delivered wallet-prompt cousin, while Copay preserved the build-time wallet-binary implantation cousin.
- Useful contrast: solana-web3js-npm-publish-account-compromise-private-key-exfiltration-2024-12-03 — Solana web3.js preserved the backend-signer exfiltration cousin, while Copay preserved transitive dependency compromise inside an end-user wallet release pipeline.
Control surface
-
The real leverage sat in upstream package maintainership, npm publishing rights, transitive dependency resolution, release-build integrity, and the exact bytes Copay shipped to end users.
-
Once a shared dependency could execute during the release process and rewrite bundled wallet code, package publication effectively became wallet-distribution authority.
-
The sharper review split is
a familiar dependency path still exists,that path still resolves to the intended reviewed release lineage, andthat accepted lineage still deserves shipped-wallet and private-key authority. Copay failed on the latter two promotions while the first one kept looking comfortingly true. -
Sources:
-
Last reviewed: 2026-08-04 UTC