The Axios Supply Chain Attack Wasn't a Hack. It Was a Con.
By Lando Calrissian | April 10, 2026 Research by Mara Jade
You probably heard that the Axios npm package was compromised. You probably heard that it had a remote access trojan. You probably heard that it affected over 100 million weekly downloads.
What you probably did not hear is how they actually did it.
This was not a zero-day exploit. This was not a sophisticated intrusion into npm’s infrastructure. This was a con — a carefully staged piece of social engineering that turned the features developers trust most into the weapons used against them. Understanding the technical details matters less than understanding the psychology. Because the psychology is what will hit you next.
Step One: Steal a Face
The attacker did not need to crack npm. They needed to crack one person.
They gained access to the npm account of jasonsaayman — one of Axios’s primary maintainers. Probably via phishing, password reuse from a prior breach, or compromised email. Once inside, they had something worth far more than any technical exploit: a trusted identity.
This is the foundation everything else is built on. From this point forward, anything published under that account inherits two years of earned trust. The community trusts Axios. Axios trusts its maintainers. The attacker is now a maintainer.
The tell was visible, if anyone had looked. Legitimate Axios releases are published via GitHub Actions with OIDC authentication and SLSA provenance attestations — a verifiable chain of custody that proves the package came from the official CI pipeline. The malicious release was published directly via CLI, with a different email address: ifstap@proton.me instead of jasonsaayman@gmail.com.
Almost nobody checked. They never do.
Step Two: Build a Fake History
The attacker did not immediately push malicious code into Axios. That would have been too obvious.
Instead, they published a new package called plain-crypto-js — and they did it clean. No malware. No backdoors. Just a functional package sitting there looking legitimate.
A day later, they published plain-crypto-js@4.2.1. Same package. But this version added a postinstall hook — a small script that runs automatically every time the package is installed. Still nothing visible in the 4.2.1 release itself that would set off alarms.
Twenty-two minutes after that, they added plain-crypto-js as a dependency in axios@1.14.1.
Here is why the sequence matters. If you look at plain-crypto-js on npm, you do not see a brand new package that appeared from nowhere. You see a package with a publication history. Version 4.2.0 came first. Then 4.2.1. It has a development trajectory. It has been around.
Humans trust things with history more than things that materialize suddenly. The attacker manufactured a history specifically to trigger that trust. The clean version existed for one purpose: to make the malicious version look like a natural update.
Step Three: Plant the Invisible Dependency
Here is the detail that nobody expected.
plain-crypto-js is a declared dependency of Axios in package.json. But it is never imported anywhere in Axios’s actual code. If you search the entire Axios codebase for plain-crypto-js, you find nothing.
This creates a specific psychological response. A developer who notices the dependency and investigates thinks: “Axios says it depends on this package, but there’s no import statement anywhere. Weird. Maybe it’s for build tooling. Probably fine.”
That gap between “npm says it’s a dependency” and “the code never uses it” is uncomfortable but not alarming. Developers see unexplained things in package.json all the time. They move on.
The dependency exists for one reason only: to trigger a postinstall hook. npm’s postinstall scripts run automatically during installation, regardless of whether the package’s code is ever imported. The attacker weaponized a convenience feature — a hook designed to automate setup tasks — and used it as a silent execution vector.
When you run npm install axios, npm downloads packages, runs hooks, and tells you: added 1 package. No mention of what the hooks did. No indication that setup.js just ran. No warning that the RAT is already deployed.
You see a single line of output. The attack is already over.
Step Four: Hide in the Automation
The postinstall hook executes setup.js. That file does the actual damage — connecting to the attacker’s command-and-control server, downloading the platform-specific payload, and deploying a remote access trojan.
Then it cleans up.
setup.js deletes itself. The package.json that contained the postinstall hook is replaced with a clean version that was sitting in the package as package.md — renamed back to erase the evidence. By the time an investigator examines the node_modules folder, they find a legitimate-looking plain-crypto-js directory. No hook. No setup.js. Nothing to see.
The cleanup was not an afterthought. It was designed. The attackers understood that the moment someone looks, the game is over. So they ensured that by the time anyone looked, there was nothing left to find.
The C2 traffic was disguised too. Network logs show requests to sfrclak.com:8000 — but the request headers include a fake prefix that makes the traffic look like npm registry communication. In a security log, this looks like standard package manager behavior. An analyst scanning for threats might see packages.npm.org/product0 and move on.
It is a social engineering attack against the security team, not just the developer.
Step Five: Trust the Platform
The real payload — the remote access trojan itself — is written in platform-native languages. PowerShell on Windows. Native C++ plus AppleScript on macOS. Python on Linux.
This is not accidental. npm security tools are looking for malicious JavaScript. Once execution hands off to PowerShell or Python, it has left the domain where npm-focused detection operates. The attacker used npm as a distribution mechanism and then immediately stepped outside the security perimeter that npm tools monitor.
The obfuscation in setup.js is also deliberate but not extreme. String encoding with base64 and XOR ciphering. Two layers, not one, because one layer is too easy to decode. But the code itself is plain JavaScript — no binary blobs, no cryptic hex. To someone glancing at it, it looks like a configuration file handling some encoded constants. Decoding it requires effort. Most people encountering it for the first time will not bother.
The attacker did not need military-grade obfuscation. They needed just enough to slow down human analysis for a few hours. The attack window was 3 hours and 59 minutes. After that, it was too late.
The Trust Hierarchy They Exploited
Every layer of this attack exploited a specific trust assumption:
Maintainer trust. When a package comes from a known account, users assume the account is still controlled by the person they trust. This assumption is usually correct. Here it was not.
Version history trust. When a package has older releases, users assume it has been around and is therefore more vetted. The clean version created this impression deliberately.
Transitive dependency trust. When a trusted library depends on something, users assume that something is also safe. This is the deepest and most dangerous assumption. Nobody audits the dependencies of their dependencies. The attack was designed to exploit exactly this gap.
Automation trust. npm runs postinstall hooks silently. Users expect installation to “just work.” That expectation made the attack invisible during execution.
Code invisibility trust. The malware ran during installation and deleted itself before the application was ever launched. By the time the developer’s code ran, there was nothing left to see.
What This Is, and What It Means
There is a category error in how most people talk about supply chain attacks. They are treated as technical problems that require technical solutions — better scanning tools, stronger code review, smarter static analysis.
The Axios attack was not a technical problem. It was a human problem. The attacker understood how npm users think, what signals they trust, what they check and what they skip, what makes them feel safe and what makes them suspicious. They designed every step of the attack around those psychological realities.
Better tooling helps. Checking SLSA attestations helps. Auditing postinstall hooks helps. But none of these are default behaviors. They require extra effort. The attacker bet correctly that most users would not do the extra work.
The lesson is not “run better scanners.” The lesson is that trust is the product, and trust can be manufactured. The npm ecosystem — like most package ecosystems — runs on implicit assumptions about who controls what and what has been vetted. Those assumptions are mostly true. Mostly is enough.
The attacker needed a three-hour window, one compromised email account, and a clean package published the day before.
They got 100 million weekly downloads.
Sources: Mara Jade intelligence analysis, npm security audit data, IOC data from community threat intelligence sharing.
Share this