
I Audited My Lockfile After the Axios Compromise. You Should Too.
Someone hijacked an Axios maintainer's npm account and published two versions with a RAT that deletes itself after install. 50 million weekly downloads. The dropper leaves no trace. Here's exactly what happened and what to check.
Someone hijacked an Axios maintainer's npm account yesterday. They published axios@1.14.1 and axios@0.30.4 with a new dependency called plain-crypto-js. That dependency ran a postinstall hook, downloaded a remote access trojan, started beaconing to a command server every 60 seconds, then deleted every trace of itself from your node_modules.
If you ran npm install on a project using axios with a caret range (^1.14.0 or ^0.30.0) between March 30 at 23:59 UTC and when npm pulled the packages on March 31, your machine may be compromised.
Axios has 50 million weekly downloads. Both the 1.x and 0.x branches were poisoned within 39 minutes of each other.
How the attacker got in
The versions don't exist on GitHub. No tags. No commits. No release branches. The last legitimate release is v1.14.0, published March 27.
The attacker got the npm credentials for jasonsaayman, one of the Axios maintainers. They used the npm CLI to publish directly, skipping the GitHub release workflow entirely. Then they changed the account's recovery email to lock the real owner out.
Here's the part that makes this worse: when community members started filing issues on GitHub reporting the compromise, those issues were being deleted. The attacker still had enough access to suppress the warning signs.
Socket's automated scanner flagged plain-crypto-js within six minutes of publication. But six minutes at 50 million weekly downloads is a lot of installs.
The attack chain, step by step
The attack is technically clean. Five stages, and each one covers the tracks of the previous one.
The dependency injection. Both malicious axios versions add plain-crypto-js@4.2.1 to package.json. Version 4.2.0 of that package was published 18 hours earlier as a clean copy of the legitimate crypto-js library. Just establishing trust. 4.2.1 added three files.
The postinstall hook. plain-crypto-js@4.2.1 carries a postinstall hook that runs node setup.js. That's the dropper. It's 3KB, heavily obfuscated with reversed base64 and XOR cipher (key: "OrDeR_7077", constant: 333). Every string is encoded: module names, file paths, the C2 URL. Nothing readable in source.
Platform-specific payload delivery. The dropper detects your OS and contacts http://sfrclak.com:8000/6202033:
On macOS: writes an AppleScript to /tmp/6202033 that downloads a Mach-O binary to /Library/Caches/com.apple.act.mond (named to look like an Apple daemon), applies an ad-hoc code signature, and runs it in the background. The RAT beacons every 60 seconds with your hostname, username, macOS version, CPU type, full process list, and a directory listing of Applications and Library folders.
On Windows: copies powershell.exe to %PROGRAMDATA%\wt.exe (disguised as Windows Terminal), then runs a hidden PowerShell script with -w hidden -ep bypass. No visible window.
On Linux: downloads a Python script to /tmp/ld.py and runs it with python3 under nohup.
Self-destruction. After launching the payload, the dropper deletes itself (setup.js), deletes the current package.json (which has the postinstall hook), then renames a pre-staged clean copy (package.md) to package.json. The installed package now looks completely normal. No dropper. No hook. No trace.
Persistent RAT. The macOS binary uses an IE8-on-Windows-XP User-Agent string (mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)) for C2 communication. That's trivially detectable in HTTP proxy logs, but most developer machines don't run one. The C2 URL isn't hardcoded in the binary. It's passed as argv[1] by the dropper, so the same RAT binary works across campaigns.
What to check right now
# Check your lockfile for compromised versions
grep -r "axios.*1\.14\.1\|axios.*0\.30\.4\|plain-crypto-js" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
# Check if the RAT binary exists on macOS
ls -la /Library/Caches/com.apple.act.mond 2>/dev/null
# Check for the Windows payload
dir "%PROGRAMDATA%\wt.exe" 2>nul
# Check for the Linux payload
ls -la /tmp/ld.py 2>/dev/null
If you find any of these, assume your machine is compromised. SSH keys, cloud credentials, API tokens, npm tokens, anything on that machine should be rotated.
The structural problem npm still hasn't fixed
I wrote about Clinejection in March. That was an AI triage bot executing a prompt injection from a GitHub issue title. Different attack vector, same structural failure: the npm ecosystem trusts the publisher, not the code.
Here's what makes this attack so effective:
npm tokens live forever by default. The attacker likely compromised a long-lived npm token. The Axios team was already using trusted publishing (which ties npm publishes to CI workflows), but the old token still worked. Two authentication paths meant two attack surfaces.
npm has no release provenance. There's no way to verify that a published package matches any specific git commit. If the GitHub repo shows v1.14.0 as the latest tag and npm shows 1.14.1 as the latest version, your only option is manual inspection. No tooling catches this automatically for most teams.
postinstall hooks run with full system access. The entire attack chain depends on node setup.js running during install. bun and pnpm don't execute lifecycle scripts by default. npm does. One line in your .npmrc would have prevented this:
ignore-scripts=true
Package managers now support minimum release age. This is the most practical defense that almost nobody uses:
# ~/.npmrc (days)
min-release-age=7
# ~/Library/Preferences/pnpm/rc (minutes)
minimum-release-age=10080
# ~/.bunfig.toml (seconds, because JavaScript)
[install]
minimumReleaseAge = 604800
A 7-day minimum age would have prevented this entirely. The malicious version existed for hours, not days.
What I actually changed today
I audited every project I maintain. None had the compromised versions, but the exercise exposed three things I should have fixed months ago:
- Two projects were still on caret ranges for axios. Pinned them to exact versions.
- My
.npmrcdidn't haveignore-scripts=true. It does now. I'll add--ignore-scriptsexceptions for the few packages that legitimately need postinstall hooks. - No minimum release age configured. Added
min-release-age=3globally. Three days isn't perfect, but it blocks the most common pattern: publish malicious version, harvest installs before anyone notices, delete the evidence.
The npm ecosystem is a trust network. The security of your project depends on the security practices of every maintainer in your dependency tree. Axios had one maintainer with a compromisable token. That's all it took.
If you use Node.js in production, audit your lockfiles today. Not next sprint. Right now. The commands are above. The RAT deletes its own evidence, so if you were in the exposure window, the only thing that will tell you is your lockfile history.
Sources
- Socket.dev: Supply Chain Attack on Axios (March 31, 2026)
- Security Boulevard: Poisoned Axios (March 31, 2026)
- GitHub Issue #10604: Community report of compromise
- GitHub PR #10591: Deprecation workflow for compromised versions
- HN Discussion: min-release-age configurations
Get new posts in your inbox
Architecture, performance, security. No spam.
Keep reading
A GitHub Issue Title Compromised 4,000 Developer Machines
Someone put a prompt injection payload in a GitHub issue title. An AI triage bot executed it, poisoned the build cache, stole npm credentials, and pushed a rogue package to 4,000 developers. The full chain took five steps.
Building Production-Ready MCP Servers
MCP servers are everywhere. Production-ready ones aren't. Here's the architecture I use after running MCP in real workloads: error boundaries, state isolation, security hardening, and scaling patterns that actually hold up.
AI Can't Audit Your Binaries Yet
The best AI model finds 49% of backdoors in compiled binaries. With a 22% false positive rate. Here's what that means for your supply chain security strategy.