On March 31st, 2026, two new Axios versions appeared on npm — 1.14.1 and 0.30.4. Same maintainer name, same package, just a patch bump. Except it wasn't the real maintainer. A compromised account published both versions after the maintainer's email was swapped to an attacker-controlled ProtonMail address.
Google Threat Intelligence linked the activity to UNC1069, a North Korean group that typically targets crypto and DeFi companies. StepSecurity and Wiz published independent technical analyses confirming the attack chain.
The malicious versions were live for about 3 hours. Axios gets ~100 million downloads per week and sits in ~80% of cloud environments.
Sources: StepSecurity · Wiz Research · Axios Media
How npm Supply Chain Attacks Work
You don't hack the developer — you hack what the developer trusts.
flowchart TD
A["Attacker compromises\nmaintainer npm account"] --> B["Publishes new\npackage version"]
B --> C["Malicious version adds\na hidden dependency"]
C --> D["Hidden dep runs\npostinstall script on install"]
D --> E["Script downloads\nOS-specific RAT"]
E --> F["Script self-destructs\nand cleans evidence"]
F --> G["RAT beacons to C2\nserver every 60s"]
G --> H["Attacker has remote\nshell access"]
style A fill:#dc2626,color:#fff
style D fill:#dc2626,color:#fff
style H fill:#dc2626,color:#fffNo zero-days. No kernel exploits. Just a postinstall script in a dependency you never asked for, running the moment you type npm install.
What Happened With Axios
The attack was pre-staged across ~18 hours:
- 05:57 UTC (Mar 30) — attacker publishes
plain-crypto-js@4.2.0— a clean decoy to build npm history - 23:59 UTC — publishes
plain-crypto-js@4.2.1— addspostinstall: "node setup.js"(the weapon) - 00:21 UTC (Mar 31) — publishes
axios@1.14.1withplain-crypto-jsinjected as a dependency - 01:00 UTC — publishes
axios@0.30.4— legacy branch, same injection, 39 minutes later - ~03:15 UTC — npm pulls both versions
There are zero lines of malicious code inside Axios itself. The only change to package.json:
"dependencies": {
"follow-redirects": "^2.1.0",
"form-data": "^4.0.1",
"proxy-from-env": "^2.1.0",
+ "plain-crypto-js": "^4.2.1"
}
That one line triggers the entire kill chain on npm install.
How You Could've Spotted It Was Fake
Every legitimate Axios 1.x release is published via GitHub Actions with npm's OIDC Trusted Publisher — cryptographically tied to a verified workflow. The malicious 1.14.1 broke that pattern:
// axios@1.14.0 — LEGITIMATE
"_npmUser": {
"name": "GitHub Actions",
"email": "npm-oidc-no-reply@github.com",
"trustedPublisher": { "id": "github" }
}
// axios@1.14.1 — MALICIOUS
"_npmUser": {
"name": "jasonsaayman",
"email": "ifstap@proton.me"
// no trustedPublisher, no gitHead, no GitHub commit
}
No OIDC binding, no corresponding Git tag, and the husky prepare script was removed (attacker bypassed the normal release tooling). There's no commit in the Axios repo for 1.14.1 — it exists only on npm.
What the RAT Does
The setup.js dropper detects your OS, downloads a platform-specific RAT from sfrclak.com:8000, then self-destructs — deleting itself and swapping package.json with a clean stub reporting version 4.2.0 instead of 4.2.1. An incident responder running npm list would see the wrong version and potentially move on.
The RAT capabilities (per Wiz):
- Remote shell execution
- Binary injection (macOS variant self-signs via
codesign) - File system browsing and process listing
- System reconnaissance
- Windows persistence via registry Run key (
MicrosoftUpdate) - Beacons to C2 every 60 seconds
Am I Affected?
# If this directory exists, the dropper ran.
# plain-crypto-js is NOT a dependency of ANY legitimate axios version.
ls node_modules/plain-crypto-js 2>/dev/null && echo "COMPROMISED" || echo "Clean"
# Check lockfiles for malicious versions
grep -rE "axios@(1\.14\.1|0\.30\.4)" pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null
# Check across all projects on your machine
find ~ -type d -name "plain-crypto-js" -path "*/node_modules/*" 2>/dev/null
If you installed axios@1.14.1 or axios@0.30.4 — assume your system is compromised. The malware executes during npm install, before you write a single line of code.
If You're Hit: What To Do
The real advice from StepSecurity: assume full compromise. Reformat the machine and rebuild from a known-good state. The RAT has remote shell access, binary injection, and file system write capabilities — you don't know what else it dropped beyond the known artifacts. Manual cleanup gives a false sense of "clean."
If you absolutely cannot reformat immediately, here's the emergency triage:
1. Isolate the Machine
Disconnect from Wi-Fi, unplug Ethernet, kill VPN. Stop the bleeding before you start cleaning.
2. Kill Active C2 Connections
lsof -i @142.11.206.73 2>/dev/null
ps aux | grep com.apple.act.mond # macOS RAT process
kill -9 <PID>
3. Remove Known Artifacts
macOS:
rm -f /Library/Caches/com.apple.act.mond
rm -f /tmp/.XXXXXX.scpt
Windows (PowerShell):
Remove-Item "$env:PROGRAMDATA\wt.exe" -Force -ErrorAction SilentlyContinue
Remove-Item "$env:PROGRAMDATA\system.bat" -Force -ErrorAction SilentlyContinue
Remove-Item "$env:TEMP\6202033.ps1" -Force -ErrorAction SilentlyContinue
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "MicrosoftUpdate" -ErrorAction SilentlyContinue
Linux:
rm -f /tmp/ld.py
4. Clean Dependencies
rm -rf node_modules/plain-crypto-js
npm install axios@1.14.0 # known safe version
Or add overrides to prevent transitive resolution back to the malicious version:
{
"overrides": { "axios": "1.14.0" },
"resolutions": { "axios": "1.14.0" }
}
5. Rotate Everything
Not optional. If the RAT executed, assume credential theft.
- npm tokens — especially if you publish packages (
~/.npmrc) - SSH keys — regenerate (
~/.ssh/) - Cloud creds — AWS (
~/.aws/), GCP, Azure CLI tokens - Git PATs — GitHub, GitLab, Bitbucket
- Docker registry creds —
~/.docker/config.json - K8s configs —
~/.kube/config - API keys and secrets — every
.envfile, every CI secret - Database passwords — all of them
- Browser-stored passwords — yes, those too
6. Scan for Persistence
# macOS — check LaunchAgents/Daemons
ls ~/Library/LaunchAgents/ /Library/LaunchDaemons/ | grep -v com.apple
# Windows — check Run keys
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Run"
# Linux — check crontab and systemd
crontab -l
ls /etc/systemd/system/ | grep -v default
IOCs — Block These Now
| Type | Value |
|---|---|
| Domain | sfrclak[.]com |
| Domain | callnrwise[.]com |
| IP | 142.11.206[.]73 |
| Port | 8000 |
| URL | hxxp://sfrclak.com:8000/6202033 |
| Registry Key | HKCU\...\Run\MicrosoftUpdate |
| File (macOS) | /Library/Caches/com.apple.act.mond |
| File (Windows) | %PROGRAMDATA%\wt.exe, %PROGRAMDATA%\system.bat |
| File (Linux) | /tmp/ld.py |
| Package | plain-crypto-js (any version) |
Prevent the Next One
Drop Axios — Use Native fetch
Hot take but hear me out: do you still need Axios?
Node.js 18+ and every modern browser ship with native fetch. If you're using Axios as a glorified HTTP client (and most of us are), you're carrying a dependency that adds attack surface for zero functional gain.
// Before — third-party dependency, supply chain risk
import axios from "axios";
const { data } = await axios.get("https://api.example.com/users");
// After — zero dependencies, built into the runtime
const res = await fetch("https://api.example.com/users");
const data = await res.json();
No dependency = no attack surface. This specific attack would have been a non-event for anyone using native fetch. Something to think about next time you reach for npm install axios.
pnpm Blocks postinstall by Default
If you're on pnpm v10+, you were already protected. Since v10, pnpm blocks all lifecycle scripts (postinstall, preinstall, etc.) by default. The plain-crypto-js postinstall script would have been silently ignored.
To allow specific trusted packages to run scripts, you explicitly approve them:
pnpm approve-builds
This adds approved packages to onlyBuiltDependencies in your pnpm-workspace.yaml. A random new dependency like plain-crypto-js would never be on that list. This is the kind of secure-by-default behavior every package manager should adopt.
Release-Age Gating
Every major package manager now supports refusing to install packages published within a cooldown window. plain-crypto-js@4.2.1 was published hours before the Axios releases. Any of these would have blocked it:
npm (v11.10+) — .npmrc:
min-release-age=7d
pnpm (v10.16+) — .npmrc:
minimum-release-age=7d
Yarn (v4.10+) — .yarnrc.yml:
npmMinimalAgeGate: "7d"
CI/CD — Block Scripts Entirely
npm ci --ignore-scripts
A 7-day cooldown + pnpm's default script blocking would have stopped this attack dead on two separate layers.
The Bigger Picture
This wasn't some script kiddie typosquatting axois. This was a state-sponsored actor who:
- Pre-staged a decoy package 18 hours in advance
- Built three OS-specific payloads
- Hijacked a top-10 npm package maintainer account
- Published across both release branches within 39 minutes
- Designed every artifact to self-destruct and spoof version numbers
The entire weapon was one line in package.json:
"postinstall": "node setup.js"
That's the uncomfortable truth about npm. The attack surface isn't some exotic vulnerability — it's npm install doing exactly what it was designed to do.
Go set min-release-age=7d. Then rotate your creds. Then get that coffee.



