What Happened
Snyk's security research team discovered over 200 malicious packages in the npm registry, exploiting dependency confusion attacks to deliver Cobalt Strike payloads. These packages used install-time execution hooks to trigger malicious behavior as soon as developers ran npm install, bypassing many security controls that focus on runtime detection. Some packages used version numbers like 99.99.99 to ensure they would be selected over legitimate internal packages with the same name.
Timeline
The timeline of infections is unclear, but the pattern is consistent: malicious packages were published to the public npm registry, developers installed them (through typosquatting, dependency confusion, or compromised internal tooling), and the payloads executed during installation before security tools could intervene.
This is the critical failure point: detection happened after publication and installation, not before.
Which Controls Failed or Were Missing
1. No Pre-Installation Package Verification
Most organizations run npm install without verifying package integrity or scanning for malicious patterns first. The install scripts executed before any security tooling could analyze the package contents.
The gap: No automated scanning of package installation scripts before they run. Your CI/CD pipeline likely treats all npm packages as trusted by default.
2. Missing Dependency Confusion Protections
Organizations with internal packages named identically to public ones had no controls to prevent npm from pulling the public version. The version number trick (99.99.99) exploited npm's default behavior of selecting the highest version number available.
The gap: No scoped package naming strategy, no private registry configuration that blocks public packages with matching names, no version pinning policy.
3. Insufficient Supply Chain Visibility
Teams had no inventory of which packages were being installed, no baseline of expected dependencies, and no alerting when new packages appeared in the dependency tree.
The gap: No software bill of materials (SBOM) generation, no dependency drift detection, no approval workflow for new dependencies.
4. Install Script Execution by Default
npm runs install scripts automatically unless explicitly disabled. This "feature" is a security liability that most teams haven't addressed.
The gap: No --ignore-scripts flag in CI/CD, no policy requiring manual review of packages with install scripts.
What the Relevant Standards Require
PCI DSS v4.0.1 Requirement 6.3.2
"Security of bespoke and custom software and software components is managed throughout their lifecycle." This includes third-party components. You need documented processes for evaluating and approving software libraries before use.
What this means for npm: You cannot treat public packages as pre-approved. Each new dependency needs a security review that includes checking for install scripts, verifying the publisher, and confirming the package does what it claims.
NIST 800-53 Rev 5 Control SA-12
"Supply Chain Protection" requires organizations to employ anti-counterfeit technologies and processes, including code review and testing of software components obtained from external sources.
What this means for npm: Your build process must include automated scanning for known malicious patterns, version anomalies, and suspicious install scripts before packages execute.
ISO/IEC 27001:2022 Control 8.30
"Outsourced development" requires security requirements to be applied to software development involving externally sourced components.
What this means for npm: You need a documented policy for third-party package selection that includes security criteria. "It's on npm" is not sufficient due diligence.
Lessons and Action Items for Your Team
Immediate Actions (This Week)
Audit your package.json files for version anomalies. Look for unusually high version numbers (anything above 10.x should trigger review) and packages you don't recognize. Run
npm lsin each project to see the full dependency tree.Enable
--ignore-scriptsin your CI/CD pipeline. Add this flag to allnpm installcommands in your build scripts. Some legitimate packages use install scripts, but you can whitelist those after manual review.Configure a private registry with blocking rules. If you use internal packages, configure your npm client to only pull packages with your organization's scope from the public registry. Use Verdaccio, Artifactory, or Azure Artifacts to proxy npm and enforce naming policies.
Medium-Term Actions (This Month)
Implement automated package scanning. Add a step in your CI/CD that scans packages before installation. Snyk's detection focused on install-time malicious logic—your scanner needs to do the same. Look for packages that make network calls, write to disk outside node_modules, or execute shell commands during installation.
Generate and monitor SBOMs. Use
npm sbomor tools like Syft to create a software bill of materials for each application. Store these artifacts and alert when new dependencies appear without a corresponding pull request.Pin all dependency versions. Stop using
^and~in package.json. Lock exact versions and update them through deliberate pull requests that trigger security review, not automatically throughnpm update.
Long-Term Actions (This Quarter)
Build a package approval workflow. Before any new npm package can be added to production code, require a security review that checks: publisher reputation, download statistics, GitHub repository activity, presence of install scripts, and license compatibility. Document approved packages in a central registry.
Implement dependency drift alerts. Configure your CI/CD to fail if the installed packages don't match your lockfile exactly. Use
npm ciinstead ofnpm installin production builds.Train developers on dependency confusion risks. Most engineers don't understand how npm resolves package names or why version 99.99.99 is suspicious. Run a workshop showing how these attacks work and what to watch for during code review.
The 200+ malicious packages Snyk found weren't sophisticated zero-days. They were install scripts that ran because no one was checking. Your controls need to assume npm is hostile territory, not a trusted source. Treat every package installation as a potential supply chain compromise until you've verified otherwise.
Cobalt Strike



