How the axios Attack Used plain-crypto-js as a Transitive Dependency
The axios attack on March 31, 2026 succeeded because of how npm handles transitive dependencies. The attacker did not need to modify axios source code. They just added one line to axios's package.json. npm did the rest.
What is a transitive dependency?
When you install a package, you install everything it depends on too — and everything those packages depend on, and so on. These indirect dependencies are called transitive dependencies. In a typical Node.js project, your package.json might list 30 direct dependencies, but your node_modules folder contains 300-500 packages. The other 470 are transitive.
Your package.json: "axios": "1.14.1" <- you asked for this npm also installs: follow-redirects <- axios needs this (legitimate) form-data <- axios needs this (legitimate) proxy-from-env <- axios needs this (legitimate) plain-crypto-js <- axios needs this (MALICIOUS - injected by attacker)
The attack mechanism - step by step
Step 1: Attacker pre-stages a "clean" package
On March 30 at 05:57 UTC, the attacker published plain-crypto-js@4.2.0 to npm. This version was completely clean — no malicious code. Its purpose was to establish a brief package history and avoid "brand-new package" security alerts that some tools trigger for packages with no history.
Step 2: Attacker publishes the malicious version
23 hours later, plain-crypto-js@4.2.1 was published with the RAT payload. The package looked identical to a legitimate crypto library. Its package.json mimicked the real crypto-js library. The only difference: a postinstall script in the scripts field that executed setup.js.
// plain-crypto-js@4.2.1 package.json (malicious)
{
"name": "plain-crypto-js",
"version": "4.2.1",
"scripts": {
"postinstall": "node setup.js" // <-- the dropper
}
}Step 3: Attacker compromises the axios maintainer account
Using credentials from the compromised jasonsaayman npm account, the attacker published axios@1.14.1 with one change from the legitimate 1.14.0: plain-crypto-js@4.2.1 was added as a runtime dependency.
// axios@1.14.1 package.json (malicious - showing only the change)
{
"dependencies": {
"follow-redirects": "^1.15.4", // legitimate
"form-data": "^4.0.0", // legitimate
"proxy-from-env": "^1.1.0", // legitimate
"plain-crypto-js": "^4.2.1" // INJECTED - RAT dropper
}
}Step 4: npm does exactly what it is designed to do
When any developer or CI/CD pipeline runs npm install axios@1.14.1, npm resolves the full dependency tree, finds plain-crypto-js@4.2.1 in axios's dependencies, downloads it, and runs its postinstall script. The RAT dropper executes in approximately 15 seconds. It contacts the C2 server, downloads platform-specific second-stage payloads, then deletes itself and replaces its package.json with a clean decoy.
npm postinstall scripts run automatically with full system permissions of whoever ran npm install. In CI/CD pipelines, that is often a service account with access to secrets, cloud credentials, and source code repositories. This is why supply chain attacks via npm are so effective — the execution happens silently during a routine operation every developer performs hundreds of times.
Why transitive dependencies are the attack surface
The attacker chose the transitive dependency route deliberately. A direct modification to axios source code would have been noticed immediately — the axios repository is watched by thousands of developers and security researchers. But a new dependency added to package.json is much less visible. Most developers never read the full dependency tree of packages they install.
This is the same attack surface exploited in the event-stream attack (2018), the ua-parser-js attack (2021), and the node-ipc attack (2022). In each case, the attacker gained access to a legitimate package and used it as a vector to deliver malicious code via the transitive dependency mechanism.
How to detect this class of attack
# Check if plain-crypto-js is in your lockfile grep "plain-crypto-js" package-lock.json # See ALL transitive dependencies of axios npm ls axios # Check when a package was added to your lockfile git log -p package-lock.json | grep "plain-crypto-js" # Verify axios has only its legitimate 3 deps npm ls --depth=1 axios
axios@1.14.0 (safe) has exactly 3 dependencies: follow-redirects, form-data, proxy-from-env. Any additional dependency in an axios release is a red flag. The presence of plain-crypto-js confirms the malicious version.
Check your lockfile for plain-crypto-js or compromised axios versions now.
Scan with PackageFix →Free · No signup · Paste your package-lock.json