April 2026
Zero Lines of Malicious Code: Inside the axios Supply Chain Attack
There are zero lines of malicious code in axios. That's what makes this attack so dangerous.
On 31 March 2026, axios versions 1.14.1 and 0.30.4 were published to npm containing a new dependency: plain-crypto-js@4.2.1. A package that didn't exist the day before. It appears nowhere in the axios source code. It was injected directly into the published npm package, bypassing the GitHub repository entirely.
axios is one of the most depended-on packages in the npm ecosystem, with over 100 million weekly downloads. Within seconds of npm install, the malicious dependency was already calling home to an attacker-controlled server, before npm had even finished resolving the rest of the dependency tree.
Precision, Not Opportunism
This was not a rushed attack. According to Socket's analysis, the malicious dependency was staged 18 hours before the poisoned axios versions were published. Three separate payloads were pre-built for macOS, Windows, and Linux. Both release branches (1.x and 0.x) were poisoned within 39 minutes of each other.
The plain-crypto-js package is an obfuscated dropper that deobfuscates its payloads at runtime, dynamically loads fs, os, and execSync to evade static analysis, executes shell commands, stages payloads into OS temp directories, and then deletes and renames its own artifacts to destroy forensic evidence. A developer who inspects their node_modules folder after the fact will find no indication anything went wrong.
Every artifact was designed to self-destruct. This is a remote access trojan delivered through the most trusted HTTP client library in JavaScript.
The Part That Should Keep You Up at Night
The payload is sophisticated. The opsec is better. But the real problem is what happened next.
When axios maintainers discovered the compromise, they tried to remediate. They couldn't. In a public GitHub issue, a collaborator stated they could not revoke access from the account responsible for the malicious publish, because the attacker's permissions exceeded their own. Any changes the maintainers made could be overridden by the compromised account. They requested npm intervention to revoke tokens, but response times for that kind of escalation are measured in hours, not minutes.
For a window of time, the most popular HTTP client on npm was controlled by an attacker, and the people responsible for maintaining it could not take it back.
This is the trust model failure that no amount of code scanning can fix. The package registry trusts the publisher. The publisher's account was compromised. The registry's access control hierarchy meant the legitimate maintainers were outranked. The entire remediation was blocked at the identity layer.
What the "AI Caught This" Claims Miss
Within hours of the disclosure, AI tooling vendors began claiming their products had detected the attack before it was publicly announced. The claims deserve scrutiny.
What an AI code review bot can realistically catch: a new, unknown dependency appearing in a lockfile diff. plain-crypto-js has zero history, zero stars, and was created the same day. If your bot reviews PRs that update lockfiles, it might flag that. And that's genuinely useful.
What it cannot catch: the upstream compromise itself. No review bot watching your repository can prevent a trusted package from being poisoned at the registry. By the time the suspicious dependency appears in your lockfile, the attack has already succeeded upstream. Flagging it in your PR is damage limitation, not detection.
The distinction matters. "We caught the supply chain attack" and "we flagged a suspicious dependency in a customer's lockfile after the attack had already happened" are very different claims. The first implies prevention. The second is incident response. Both have value. Only one is being marketed.
Why npm install Is a Trust Decision
Every npm install that doesn't pin exact versions is a trust decision. You're trusting that every maintainer of every package in your dependency tree still controls their account, hasn't been compromised, and hasn't published anything malicious since your last install.
Andrej Karpathy put it well: his system resolved axios to an unaffected version only because he happened to install it days earlier. The dependency wasn't pinned. If he'd run the same command hours later, he'd have pulled the compromised version. Luck is not a security control.
The defaults of package management are designed for convenience, not safety. Latest version by default. Scripts execute by default. No minimum age requirement by default. Each of these defaults is an attack surface.
What You Should Do Now
Immediately:
- Search your lockfiles (
package-lock.json,yarn.lock,pnpm-lock.yaml) foraxios@1.14.1,axios@0.30.4, orplain-crypto-js. Lockfile diffing is the reliable detection method. Do not rely on inspectingnode_modulesdirectly, because the payload renames a pre-stagedpackage.mdtopackage.jsonpost-install andsetup.jsself-deletes, leaving no visible evidence in the installed files - Check feature branches and open PRs, not just
main - If found, remove them or roll back to a known safe version (1.14.0 or earlier)
- Check network logs for outbound connections to
sfrclak.com:8000(the C2 server used by this attack) - If the compromised version was installed, assume the machine is compromised. Check for payloads in OS temp directories and Windows ProgramData
This week:
- Pin your dependencies. Exact versions in
package.json, committed lockfiles, andnpm ci(notnpm install) in CI/CD - Add
ignore-scripts=trueto your.npmrc. The axios attack used a postinstall script. This alone would have mitigated it. Note: some legitimate packages need install scripts, so test this change - Audit your dependency tree for packages you don't recognise.
npm lsis your friend
This quarter:
- Implement minimum release age across your package managers. A 7-day minimum release age would have prevented this attack entirely. The malicious versions would have been detected and removed long before the age threshold expired. Yes, this delays legitimate security patches too, but compromised packages are pulled from registries constantly, while actively exploited zero-days in your direct dependencies are comparatively rare. When one does land, you can temporarily override the setting.
- Restrict network access in development environments, CI/CD, and production. Minimum release age buys you time. Network filtering is the actual defence. If a compromised package can't phone home, the payload is inert.
- Build supply chain visibility. You need to know which of your projects depend on axios, at which version, across every repository and environment. Discovering this repo by repo after the fact is not a strategy.
This Wasn't a One-Off
Threat intelligence analysis by Gen Threat Labs reveals the axios npm attack was just the latest vector in a 5-month social engineering campaign by the same actor. The domain registration timeline tells the story:
- November 2025:
youngjj[.]comregistered for infrastructure preparation - January 2026: Fake Zoom delivery via
uz02web[.]us - February 2026: VBS dropper loading a PowerShell RAT, "Chrome Update" LNK persistence
- March 25: Fake Teams delivery via
msteamcall[.]com - March 31: axios npm supply chain attack via
sfrclak[.]com
Same template every time: system.bat in C:\ProgramData, PowerShell download cradle, C2. The infrastructure overlaps confirm it: shared IPs, shared Let's Encrypt certificates, all domains registered through Namecheap, all registrant emails on Proton-family accounts matching the axios npm publishers.
The npm supply chain vector was not the opening move. It was a rotation. When social engineering via fake meeting invites ran its course, the actor pivoted to supply chain poisoning. The next rotation will be something else.
The Pattern Repeats
Two supply chain attacks on top-tier open-source packages in a single week. LiteLLM on PyPI. axios on npm. Different ecosystems, different attack vectors, same pattern: compromise a trusted publisher, inject a malicious dependency, harvest credentials from the install base before anyone notices.
The gap between "published" and "detected" is shrinking thanks to security scanning, but the gap between "published" and "installed on your machine" is measured in seconds. Automated pipelines, development environments, and AI coding agents all pull latest versions without human review. That's the window these attacks exploit.
The only controls that work are the ones that exist before npm install runs: pinned versions, minimum release age, disabled scripts, and network restrictions. Everything else is incident response.
At ThreatControl, we help organisations understand and control their supply chain exposure across their full dependency tree. Our Security Suite includes supply chain risk assessment and pre-CVE intelligence, and our Fractional CTO service helps development teams harden their pipelines and package management. Get in touch.