In September 2018, one of the most consequential software supply chain attacks in npm's history unfolded inside a package almost nobody thought twice about: event-stream, a tiny Node.js streaming utility downloaded roughly two million times a week. A new "co-maintainer" quietly added a malicious sub-dependency, flatmap-stream, that carried an encrypted payload designed to steal cryptocurrency wallet keys from a specific downstream application — the Copay/BitPay Bitcoin wallet. For over two months, that payload rode silently through thousands of CI pipelines and production builds before anyone noticed. The event-stream npm hack is now the textbook case study for what a targeted, trust-based supply chain compromise looks like, and it remains one of the clearest arguments for why dependency provenance and reachability analysis matter as much as patching known CVEs.
Affected Versions and Components
The attack had a narrow blast radius by design, which is part of what made it so dangerous — it wasn't noisy, it was surgical.
- event-stream — versions from 3.3.6 onward (published September 8, 2018) pulled in the malicious
flatmap-streamdependency. Versions before 3.3.6 are clean. - flatmap-stream — version 0.1.1 was the malicious package itself, published by the attacker specifically to be pulled in by event-stream. It had no legitimate prior history; it existed solely to deliver the payload.
- Downstream consumers — the payload was engineered to activate only inside applications matching the package name and file structure of Copay and BitPay wallet apps, specifically Copay desktop/mobile builds in the 5.0.2 through 5.1.0 range that bundled the tainted event-stream release. The malicious code checked for the presence of
copay-dashin the dependency tree before decrypting and executing its real logic, meaning the vast majority of the roughly two million weekly downloaders of event-stream never triggered the payload at all — but every one of them was carrying it. - Because event-stream was itself a transitive dependency of countless other packages (it was used by build tools, testing frameworks, and general-purpose utility libraries), the exposure surface extended well beyond anyone who explicitly listed it in
package.json.
Severity, CVSS, and KEV Context
This incident predates — and doesn't cleanly fit — the traditional CVE/CVSS/EPSS scoring model, and that gap is itself an important lesson. There is no MITRE CVE ID commonly cited for the malicious flatmap-stream payload; it was tracked instead through an npm Security Advisory (npm advisory #803) and a corresponding GitHub Security Advisory for event-stream, rather than through the formal CVE pipeline. No CVSS base score was ever assigned because CVSS is built to describe vulnerabilities in code — a flaw an attacker exploits — not a case where the "vulnerability" is the maintainer trust model itself and the package's shipped behavior was intentionally malicious. Likewise, EPSS (which predicts exploitation likelihood for known CVEs) and the CISA Known Exploited Vulnerabilities catalog (which tracks exploited vulnerabilities in operational use) don't apply cleanly here either, since this wasn't a bug being exploited in the wild — it was a deliberately planted backdoor with a live, working exploit from day one.
That absence of a clean severity score is exactly why event-stream is worth revisiting today: a CVSS-driven, "patch what's scored highest" posture would have completely missed it. The real severity signal was behavioral — a brand-new, purpose-built dependency with no track record, obfuscated code, and a narrowly-targeted trigger condition aimed at exfiltrating private keys and wallet seeds to an attacker-controlled server whenever a qualifying wallet held a sufficiently large balance.
Timeline of the Attack
- 2013 — Dominic Tarr publishes event-stream, which grows into one of the most widely-used utility modules in the npm ecosystem.
- Mid-2018 — Tarr, no longer actively using the package, is approached by a GitHub user going by "right9ctrl," who offers to help maintain it. Publish rights are handed over — a common, largely informal practice in open source that the attacker exploited by design.
- September 8, 2018 — right9ctrl publishes event-stream v3.3.6, introducing a new dependency, flatmap-stream v0.1.1, which contains the encrypted malicious payload.
- September–November 2018 — The tainted version propagates through the npm registry undetected, pulled into CI builds, developer machines, and application bundles worldwide, including Copay wallet releases.
- November 20, 2018 — Developer Jayden Seric (later credited alongside other community members) flags flatmap-stream's suspicious, minified, and functionally unnecessary code in a public GitHub issue, kicking off a rapid community investigation.
- November 25–26, 2018 — Researchers decrypt the payload, confirming it targets Copay wallet installations to steal private keys and seed phrases. npm removes flatmap-stream from the registry, and event-stream is patched to strip the malicious dependency.
- Late November 2018 — BitPay publishes a security advisory, urges affected Copay users to move funds to a new wallet, and ships patched app versions. The broader npm community begins pushing for mandatory 2FA on publish rights and more scrutiny of maintainer handoffs.
- Aftermath — The incident becomes a founding case study for npm's later security investments (audit tooling, publish-token scoping, provenance attestations) and for the broader software-supply-chain-security industry that emerged in its wake.
Remediation Steps
If you're auditing historical exposure or building defenses against the same attack pattern today, focus on these concrete actions:
- Inventory for the exact malicious versions. Search your dependency trees — direct and transitive — for
event-stream@3.3.6(or any version depending onflatmap-stream) andflatmap-stream@0.1.1. Don't stop atpackage.json; walk the full resolved tree, since event-stream was almost always a transitive dependency. - Rotate any potentially exposed secrets. If your build pipeline or a shipped application ever resolved the malicious version — even without matching the Copay trigger condition — treat any private keys, wallet seeds, or credentials handled in that build environment as potentially exposed and rotate them.
- Pin and lock dependencies. Use committed lockfiles (
package-lock.json,npm-shrinkwrap.json, oryarn.lock) and install withnpm cirather thannpm installin CI, so a new malicious publish of an existing package can't silently slip into a build. - Disable arbitrary install-time script execution where possible. Malicious payloads frequently ride in
postinstall/preinstallscripts or, as here, in the module's runtime code itself. Running installs with--ignore-scriptsin CI, combined with runtime behavioral checks, reduces the blast radius of a compromised package. - Scrutinize new or transferred maintainership. A sudden ownership handoff on a heavily-depended-upon package, followed shortly by a new sub-dependency with zero download history, is close to a textbook indicator of this attack pattern. Treat it as a signal worth investigating, not routine housekeeping.
- Verify package integrity and provenance. Favor packages published with npm provenance attestations or Sigstore-backed signing, and validate checksums against known-good hashes rather than trusting the registry blindly.
- Enforce registry-level hardening. Require 2FA for any account with publish rights to packages you depend on where you have influence, and use scoped, short-lived publish tokens rather than long-lived credentials.
How Safeguard Helps
Attacks like event-stream succeed by hiding a narrowly-triggered payload inside a dependency graph too large for any team to review by hand — which is precisely the problem Safeguard is built to solve. Our reachability analysis would have flagged that flatmap-stream's malicious code path, while present in your build, only executes under the Copay-specific trigger condition, letting teams instantly separate "this malicious package is in my tree" from "this malicious code can actually run in my application" and prioritize accordingly. Griffin AI continuously profiles new and transferred package publishes for the exact anomaly pattern seen here — a fresh, unvetted sub-dependency, obfuscated logic, and a maintainer handoff with no track record — surfacing that risk before it reaches production rather than two months after. Continuous SBOM generation and ingestion means that when the next event-stream-style incident is disclosed, Safeguard can tell you within minutes, across every repository and build artifact you own, exactly where the affected version lives — direct or transitive. And where remediation is needed, Safeguard's auto-fix PRs open the upgrade or pin change automatically, turning a manual, error-prone dependency hunt into a same-day fix.