Skip to main content
8 Million Downloads of Compromised CodeIncident
4 min readFor Security Engineers

8 Million Downloads of Compromised Code

On November 26, 2018, security researchers discovered that event-stream—a widely-used npm package downloaded roughly 2 million times per week—had been compromised. The malicious code, hidden in a dependency called flatmap-stream, was downloaded nearly 8 million times over 2.5 months before detection. The attack specifically targeted bitcoin wallets in applications using the package.

This wasn't a sophisticated supply chain attack exploiting zero-days. It was a trust handoff that went wrong.

Timeline

September 2018: The original maintainer of event-stream transferred ownership to a new contributor named right9ctrl, who had submitted several legitimate pull requests.

September 5, 2018: right9ctrl published version 3.3.6 of event-stream, adding flatmap-stream as a dependency. The malicious code in flatmap-stream was designed to inject itself into applications and steal bitcoin credentials.

September-November 2018: For 2.5 months, the compromised package spread through the npm ecosystem. Developers incorporated it into their projects during routine updates.

November 26, 2018: Security researchers identified the malicious code after investigating suspicious behavior in applications using event-stream.

Which Controls Failed

Dependency vetting: Teams installed event-stream based on its historical reputation without verifying the new maintainer's identity or reviewing code changes.

Change monitoring: The addition of flatmap-stream as a new dependency didn't trigger alerts. Most teams lack tools to flag when established packages introduce new transitive dependencies.

Code review of dependencies: The malicious code was in a dependency of a dependency. Few organizations review code at that depth, especially for widely-used packages.

Maintainer verification: npm had no formal process to verify maintainer identity or ensure continuity of control. Ownership transfers occurred without requiring identity proof or security review.

Runtime behavior monitoring: Applications didn't detect that event-stream was making network calls to exfiltrate data or accessing bitcoin wallet files—behaviors that should have triggered alerts.

What Standards Require

NIST 800-53 Rev 5 SR-3 (Supply Chain Protection) requires organizations to "employ security safeguards to protect against supply chain threats." This includes verifying software integrity and monitoring for unauthorized changes.

ISO/IEC 27001:2022 Control 8.30 (Outsourcing) requires that "information security requirements for the use of outsourced development shall be agreed and documented." You need documented criteria for accepting new versions, especially when maintainership changes.

PCI DSS v4.0.1 Requirement 6.3.2 states that "security of bespoke and custom software and application code is maintained throughout the application lifecycle." This includes third-party components. If you process payment data and use npm packages, you must assess new versions before deployment.

OWASP ASVS v4.0.3 V14.2.1 requires that "all components are up to date with proper security configuration and version." This means having a process to evaluate updates for both security patches and new risks.

SOC 2 Type II CC6.1 (Logical and Physical Access Controls) requires that "the entity implements logical access security software, infrastructure, and architectures over protected information assets." Your dependency management process is part of your access control architecture.

Lessons and Action Items

1. Treat maintainer changes as critical events

Monitor for ownership transfers in your critical dependencies. When a package you depend on changes maintainers, that's a security event requiring review. Tools like Socket and Snyk can alert you to maintainer changes, but you need a defined response process.

Action: Document which packages are critical to your application. Set up alerts for maintainer changes in these packages. When an alert fires, freeze updates until you review the new maintainer's history and the code changes they've introduced.

2. Implement dependency pinning and review gates

Don't let dependencies auto-update in production. Use lock files (package-lock.json, yarn.lock) to pin exact versions, and require explicit approval before updating.

Action: Configure your CI/CD pipeline to fail if dependency versions change without a corresponding update to your lock file. Require a security team member to approve dependency updates that introduce new transitive dependencies or change maintainers.

3. Monitor for behavioral changes

The malicious code in flatmap-stream made network calls and accessed filesystem paths that event-stream never touched before. Runtime application self-protection (RASP) tools or eBPF-based monitoring can detect when dependencies start exhibiting new behaviors.

Action: Baseline the network and filesystem behavior of your application in a staging environment. Configure monitoring to alert when dependencies access unexpected resources—particularly network endpoints outside your approved domains or filesystem paths containing credentials.

4. Scan dependencies at multiple depths

Snyk and similar tools can identify known vulnerabilities, but you need to scan your entire dependency tree, not just direct dependencies. The malicious code was in flatmap-stream, which was a dependency of event-stream.

Action: Run npm list or yarn why to map your complete dependency tree. Configure your security scanning to analyze all transitive dependencies. For critical applications, consider using tools that perform behavioral analysis of dependencies.

5. Establish a software bill of materials (SBOM)

You can't protect what you don't inventory. Generate and maintain an SBOM that lists every component in your application, including transitive dependencies.

Action: Use tools like CycloneDX or SPDX to generate SBOMs for each build. Store these with your release artifacts. When a vulnerability is disclosed, you can quickly determine which applications are affected.

6. Implement least privilege for build processes

Your build pipeline shouldn't have access to production credentials, bitcoin wallets, or sensitive data. Even if malicious code enters through a dependency, limit what it can access.

Action: Review what your CI/CD runners can access. Remove production credentials from build environments. Use short-lived tokens with minimal scope. If your build process doesn't need network access beyond your package registry, block outbound connections.

The event-stream incident wasn't inevitable. Each control failure represents a decision point where different tooling or processes could have prevented or detected the compromise. The question isn't whether your dependencies are trustworthy—it's whether you have controls in place to detect when they stop being trustworthy.

npm security best practices

Topics:Incident

You Might Also Like