Lesson 2: Anatomy of npm Exploits — Real-World Attacks
Introduction
In Lesson 1, we learned how package.json and versioning work. Now let's see what happens when those mechanisms are weaponized. This lesson walks through real-world npm exploits — from simple vulnerabilities in trusted packages to sophisticated supply chain attacks that compromised billions of downloads.
By the end, you'll understand the major categories of npm vulnerabilities, how attackers exploit them, and why traditional security tools often miss the most dangerous threats.
Categories of npm Vulnerabilities
Research analyzing over 31,000 vulnerabilities across ecosystems from 2017 to 2025 reveals clear patterns in the npm landscape. Let's examine each category, ordered by prevalence.
1. Embedded Malicious Code (CWE-506)
This is the single largest category, representing nearly half of all npm vulnerabilities. Unlike bugs or design flaws, these are packages published with harmful intent from the start.
Common tactics include:
Typosquatting: Publishing packages with names like
lodahs(misspelling oflodash) orexpres(misspelling ofexpress). Research shows over 71% of malicious packages use long names with more than 10 characters, and 67% include dashes to mimic legitimate naming conventions.Dependency Confusion: Attackers publish packages to the public npm registry using the same name as a company's internal private packages. When a developer's build system checks the public registry first, it pulls the attacker's malicious version. Alex Birsan famously demonstrated this by compromising Apple, Microsoft, and dozens of other companies.
Star-jacking: Cloning popular repositories on GitHub and linking the clone from a malicious npm package to build false credibility. The GitHub stars belong to the legitimate project, but the npm package contains malware.
Slopsquatting: A newer attack vector exploiting AI coding assistants. When LLMs hallucinate non-existent package names, attackers create malicious packages with those exact names, knowing developers may blindly install AI-suggested packages.
2. Supply Chain Attacks
Rather than publishing new malicious packages, supply chain attacks compromise existing trusted packages. Because these packages already have established user bases and trust, the blast radius is enormous.
As Liran Tal, Director of Developer Advocacy at Snyk, explains on the Señors @ Scale podcast:
"What we've seen more recently that's been impactful on supply chain security has been compromising maintainers, but also not stopping there, but actually making it into a worm that when you install it, it captures your npm tokens and then publishes a new package on your behalf. And that adds the malware. So there's a chain of events, like a worm that spreads, and all the packages of all compromised maintainers get breached and published. And that kind of continues like the domino effect."
Why is npm the primary target? Not because Node.js is inherently less secure, but because of accessibility:
"To npm, you literally need nothing. Some throwaway email account, you create an npm account and you publish it. That's it. So it's much more accessible. And obviously, because it is, it's going to be very lucrative to attackers."
3. Prototype Pollution (CWE-1321)
A JavaScript-specific vulnerability class with over 560 reported cases in npm. JavaScript objects inherit properties from prototypes. When attackers manipulate __proto__, constructor, or prototype attributes, they inject malicious properties that affect every object in the application. Consequences range from denial of service to full remote code execution.
4. Regular Expression Denial of Service (ReDoS)
Inefficient regular expressions that consume excessive CPU when processing crafted input. An attacker can crash applications or cause severe performance degradation. The cross-spawn package vulnerability (CVE-2024-21538) affected millions of projects through transitive dependencies.
5. Path Traversal
Allows attackers to access files outside intended directories by manipulating file paths. Despite being well-documented since the 1990s, these vulnerabilities continue to appear in npm packages.
6. MCP Server Exploitation (Emerging)
With the rise of AI coding agents, MCP (Model Context Protocol) servers are a new attack surface. MCP servers are npm packages consumed by AI agents, and they carry all the same supply chain risks plus new ones unique to the AI context. As Liran Tal explains:
"That MCP thing is literally a package. So it has the same risks as if you install an npm package. But there are very inherent attack vectors that are unique to MCPs — you could actually introduce context into the AI agent that you've now tainted. You can overwrite other tools from other MCPs. You can shadow them."
— (Señors @ Scale podcast)
MCP servers also expand the credential attack surface — their config files often contain API tokens, and malware that previously only scanned .env files will now also target MCP configuration files for credentials.
Case Study 1: The serialize-javascript Vulnerability (CVE-2020-7660)
Let's examine a concrete exploit to understand how a vulnerability in a single package can put millions of users at risk.
The Package
serialize-javascript is an open-source project created by Yahoo that serializes JavaScript to a superset of JSON, handling expressions, dates, and functions. It had over 16 million downloads and was a dependency of more than 840 projects, including Ruby on Rails and Webpacker.
The Vulnerability
The vulnerability allowed remote attackers to inject and execute arbitrary code through the deleteFunctions function in index.js. This is called Remote Code Execution (RCE) — one of the most severe vulnerability types.
How It Worked
The root cause was the library's use of JavaScript's eval() function. Under normal circumstances, serializing an object like {"foo": "example", "bar": "test"} produces a safe string. But an attacker who could control the input and predict the library's internal UID could craft an object that, when serialized and then deserialized, would execute arbitrary JavaScript code.
Here's the dangerous pattern:
In practice, this meant an attacker could steal credit card information from browsers and send it to a server they controlled.
The Fix
Version 3.1.0 patched the vulnerability by ensuring placeholders weren't preceded by a backslash and incorporating UIDs with higher entropy. The vulnerability was assigned a CVSS score of 8.1 ("Important"), just short of "Critical."
The Lesson
The serialize-javascript vulnerability illustrates a common pattern: a widely-used, trusted package harboring a critical flaw. If your package.json specified "serialize-javascript": "^2.0.0", you were vulnerable. If it specified "serialize-javascript": "^3.0.0", the caret range would have automatically pulled in the fix once 3.1.0 was released. But between the vulnerability's discovery and the patch release, every user was exposed.
Case Study 2: The September 2025 npm Supply Chain Attack
This attack compromised 19 of the most popular packages in the entire npm ecosystem — including chalk, debug, ansi-styles, and strip-ansi — collectively downloaded over 2 billion times per week.
The Attack Vector: Social Engineering
The attack began with a phishing email sent from npmjs.help, a fraudulent domain designed to impersonate the official npm registry (npmjs.com). The email tricked a maintainer into "updating" their 2FA credentials on a fake login page. The captured credentials and authentication tokens were sent to an attacker-controlled endpoint.
The Compromise
With the maintainer's credentials, the attackers published new versions of 19 packages, all containing identical malicious payloads. The compromised versions included:
chalk@5.6.1debug@4.4.2ansi-styles@6.2.2supports-color@10.2.1strip-ansi@7.1.1ansi-regex@6.2.1- And 13 more
The Payload: A Crypto Drainer
The injected code functioned as a "Web3 drainer" — a man-in-the-browser attack designed to hijack cryptocurrency activity. Once active, it:
- Monitored for connected crypto wallets (MetaMask, Phantom, etc.)
- Hooked into browser APIs like
fetchandXMLHttpRequest, plus Web3 APIs likewindow.ethereum - Swapped addresses when users initiated cryptocurrency transactions, redirecting funds to the attacker
- Manipulated on-screen data so deposit fields and QR codes showed the attacker's address while appearing legitimate
The address-swapping logic was particularly sophisticated, using a string similarity algorithm (Levenshtein distance) to find attacker addresses that looked nearly identical to legitimate ones. It targeted Ethereum, Bitcoin, Solana, Tron, Litecoin, and Bitcoin Cash.
How You Could Be Affected
There were three main infection paths:
- First-time use: Installing one of the compromised packages for the first time pulled the malicious version.
- Container rebuilds: Rebuilding a container without a lockfile pinning versions would pull the latest (compromised) versions.
- Running
npm update: This command bumps versions inpackage.jsonto the latest, pulling in compromised versions.
Indicators of Compromise (IOCs)
You can check your own environment with this command:
The Lesson
Even the most popular, trusted packages aren't immune. The attack demonstrated that:
- 2FA alone isn't sufficient (phishing can bypass TOTP-based 2FA)
- A single compromised account can cascade to billions of affected installs
- Lockfiles and
npm ciwould have protected teams that had them in place - Reactive security tools (like
npm audit) couldn't detect this because the malicious code was in new versions of legitimate packages, not in packages with known CVEs
Case Study 3: The Shai-Hulud Worm
The Shai-Hulud attack represented a terrifying evolution in npm attacks: the first successful self-propagating worm in the npm ecosystem.
What Made It Different
Previous attacks required the attacker to manually compromise each package. Shai-Hulud was different — it was designed to spread automatically. Once a developer's npm tokens were stolen, the malware attempted to publish malicious versions of every other package that developer maintained, without any additional human intervention.
The Scale
The initial campaign compromised 796 packages affecting over 20 million weekly downloads. It specifically targeted CI/CD environments, using GitHub Actions workflows to propagate across repositories.
The Lesson
Shai-Hulud forced the ecosystem to reconsider fundamental assumptions about trust. Package managers like pnpm and Yarn added settings for delayed dependency updates. GitHub began planning a more secure npm supply chain. CISA issued an official alert recommending organizations pin dependencies and rotate all developer credentials.
Case Study 4: The event-stream Incident
One of the most famous npm attacks predates the recent wave. In 2018, a developer who maintained the popular event-stream package (downloaded 2 million times per week) transferred ownership to a new contributor who seemed helpful and had been submitting PRs.
The new maintainer added a dependency on a package called flatmap-stream, which contained encrypted malicious code targeting a specific Bitcoin wallet application (Copay). The attack was highly targeted — the malicious code only activated when running inside Copay, making it extremely difficult to detect through automated scanning.
The Lesson
This attack revealed the human element of open-source trust. The original maintainer was burned out and handed off the project to someone who seemed helpful. This pattern — targeting overworked, solo maintainers — remains a primary attack vector.
Liran Tal, who was deeply involved in npm security research at the time, reflects:
"Event-stream was one of the ones from 2019 that I got much more in the weeds into supply chain security and malicious packages. It really left an impression on me because it was like a spearfishing kind of attack where they socially engineered and understood what are the projects that actually use the dependency."
The sophistication here is key — the attacker didn't just inject random malware. They researched the downstream consumers, identified a high-value target (a Bitcoin wallet), and crafted an encrypted payload that only activated in that specific context.
How Vulnerabilities Enter Your Codebase
Understanding the infection paths helps you prioritize defenses:
Maintainer Account Compromise
Attackers steal credentials through phishing, credential stuffing, or social engineering. One compromised account can cascade into billions of affected downloads.
How easy is this? Liran Tal recounts a chilling experiment:
"There was a Node.js TSC fellow who was conducting an experiment, sort of like research on npm, finding weak credentials, and you'd be surprised — he was able to get account access to packages like React, Debug, Express, Koa. Sometimes a few times, because you have a bunch of maintainers on the package account on npm. And it's enough to just find one maintainer that does have access but doesn't use good security hygiene."
A single maintainer with a weak or reused password is all it takes to compromise a package used by millions.
Automated Installation
CI/CD pipelines running npm install without version pinning pull malicious updates automatically. This is why npm ci with a committed lockfile is critical.
Transitive Dependencies
Your app may have 10 direct dependencies, but the average npm project pulls in 79 transitive dependencies. A vulnerability anywhere in that tree affects you.
Post-Install Scripts
npm packages can define scripts that run automatically when you install them (preinstall, postinstall). Malicious packages use these to execute code the moment you run npm install — before you've had any chance to review the code.
The Vulnerability Timeline Problem
There's an inherent gap between when a vulnerability exists and when it's known:
During the entire window before a CVE is published and added to npm's advisory database, npm audit sees nothing. This is the zero-day window, and it's where the most dangerous attacks live.
Key Takeaways
- Nearly half of all npm vulnerabilities are intentionally malicious code, not accidental bugs. Your threat model must account for attackers, not just negligent developers.
- The most dangerous attacks compromise trusted packages — your lockfile and
npm ciare your primary defenses against pulling in a compromised version. - Post-install scripts can execute malicious code at install time. Consider adding
ignore-scripts=trueto your.npmrcand selectively enabling scripts only for packages that need them. - The event-stream incident showed that trust is the weakest link in open source. A helpful new contributor might be an attacker.
- Automated tools like
npm auditonly catch known vulnerabilities. They can't detect zero-day attacks or novel malicious code. - Supply chain attacks are escalating in both frequency and sophistication, with self-propagating worms now a reality.
What's Next
Now that we understand what can go wrong, Lesson 3 will cover how to defend against these attacks: from lockfile discipline and npm audit to private registries, ignore-scripts, and the layered security model that every frontend developer should adopt.