Table of Contents
- What Happened
- Why This One Hits Different
- The Full Timeline — Minute by Minute
- How They Got In — The Token Problem
- The Attack Chain — Step by Step
- What the Malware Actually Does
- The Self-Destruct Mechanism
- Who Did This
- The Damage — How Bad Was It
- How It Was Caught
- Am I Affected? How to Check
- The Bigger Picture — npm's Trust Problem
- What Developers Should Do Now
- What This Attack Teaches Us
What Happened
On March 31, 2026 — just after midnight UTC — someone published two new versions of Axios to npm:
-
axios@1.14.1(taggedlatest) -
axios@0.30.4(taggedlegacy)
Both versions looked normal. Same README. Same API. Same everything — except for one tiny addition to package.json: a new dependency called plain-crypto-js@4.2.1.
That package had never existed before that day. Its sole purpose was to execute a postinstall script that silently dropped a Remote Access Trojan on your machine — macOS, Windows, and Linux. All three. Within seconds of running npm install.
No user interaction required. No warning. No prompt. You install your dependencies like you do every day, and a North Korean hacking group now has a backdoor on your machine.
The malicious versions were live for approximately 2 hours and 54 minutes before npm pulled them.
Let that sink in. Less than 3 hours. And in that window, every CI/CD pipeline, every developer laptop, every Docker build that happened to run npm install with Axios — potentially compromised.
Why This One Hits Different
npm supply chain attacks aren't new. We've seen this movie before:
- event-stream (2018) — A maintainer handed off control to a stranger who injected crypto-stealing malware targeting Copay wallets.
- ua-parser-js (2021) — Compromised maintainer account, cryptominer injected. 8 million weekly downloads affected.
- colors.js / faker.js (2022) — The maintainer himself sabotaged his own packages in protest. Broke thousands of apps.
- xz-utils (2024) — A years-long social engineering campaign to backdoor a Linux compression library. Caught by accident.
Each one was a wake-up call. And each time, the ecosystem hit snooze.
But Axios is different. And this time, there's no sleeping through it.
Scale: Axios gets roughly 100 million downloads per week. It's in virtually every JavaScript project you've ever touched — React apps, Node.js backends, mobile apps, enterprise dashboards. If you write JavaScript, Axios is somewhere in your dependency tree. Probably more than once.
Sophistication: This wasn't some kid typosquatting axois and hoping someone misspells it. This wasn't a disgruntled maintainer rage-quitting. This was a coordinated, multi-phase operation — staged dependencies published 18 hours in advance, cross-platform RAT payloads pre-built for three operating systems, and a self-destruct mechanism that erases its own forensic evidence after execution. Whoever planned this knew exactly what they were doing.
Attribution: North Korean state-sponsored hackers. A government-backed group that has stolen hundreds of millions in crypto over the past decade. They're targeting developer machines because that's where the keys to the kingdom live — crypto wallets, cloud credentials, API secrets, production access.
Speed: 39 minutes between the two malicious publishes. Both release branches poisoned in one operation. This wasn't improvised. This was rehearsed.
The Full Timeline — Minute by Minute
Here's how the whole thing unfolded. Pay attention to how deliberate the timing is:
March 30, 2026
05:57 UTC — Clean "plain-crypto-js@4.2.0" published to npm
└─ Decoy. Establishes the package as "real."
No malicious code. Just a clean placeholder
so the package doesn't look brand new when
it shows up in Axios's dependencies.
23:59 UTC — Malicious "plain-crypto-js@4.2.1" published
└─ NOW it has the payload. Staged 18 hours
after the clean version. Patient.
March 31, 2026
00:21 UTC — axios@1.14.1 published (tagged "latest")
└─ Adds plain-crypto-js@4.2.1 as a dependency.
Published using compromised npm token.
01:00 UTC — axios@0.30.4 published (tagged "legacy")
└─ Same injection. Both release branches poisoned.
39-minute gap between the two publishes.
~03:15 UTC — npm unpublishes both malicious Axios versions
└─ 2 hours, 54 minutes of exposure.
18:30 UTC — Huntress publishes analysis linking attack
to North Korean state-sponsored actors
The timing is deliberate. Just after midnight UTC — that's the middle of the night in Europe and late afternoon/evening in the US. Fewest possible eyeballs on npm. Maximum window before someone notices something's off.
These people weren't rushing. They published the decoy 18 hours early, waited for the right moment, and executed both publishes within 39 minutes. That's operational discipline.
How They Got In — The Token Problem
This is the part that should make every open source maintainer uncomfortable.
The attacker got their hands on a long-lived classic npm access token belonging to jasonsaayman — the primary Axios maintainer. With this single token, they could publish new versions of Axios directly to npm. No GitHub access needed. No code review. No CI/CD pipeline. Just... publish.
Here's what makes it worse: Axios had already set up OIDC Trusted Publishing — a newer security feature where npm cryptographically verifies that a publish came from an authorized GitHub Actions workflow. This is literally designed to prevent the exact attack that happened.
But there was a gap. The GitHub Actions pipeline was passing both OIDC credentials and a classic NPM_TOKEN as environment variables. And when npm sees both? It uses the classic token. The newer, more secure system gets ignored.
So the attacker didn't need to touch GitHub. Didn't need to push a single line of code to the repository. They just used the stolen token to publish directly to npm — walking straight past every CI/CD security check like they didn't exist.
You can actually tell the difference if you look closely. Legitimate Axios releases have cryptographic OIDC binding to verified GitHub Actions workflows. The malicious versions? No OIDC signature. No corresponding git commit. No release tag. Just a raw publish from a token that should have been revoked a long time ago.
The attacker also swapped the npm account's email to ifstap@proton.me — so the real maintainer wouldn't get any notification emails and couldn't easily recover the account. Thoughtful. Terrifyingly thoughtful.
The takeaway is uncomfortable but simple: A long-lived npm token is a skeleton key. If it leaks — through a compromised laptop, a stolen backup, a misconfigured CI secret — everything that token can publish is at risk. OIDC is the right answer, but only if you actually revoke the classic tokens. Leaving both active is like installing a deadbolt on your front door while leaving the window open.
The Attack Chain — Step by Step
Here's exactly what happens on your machine when you run npm install with one of the compromised versions. The whole thing takes seconds:
npm install axios@1.14.1
│
├─ npm resolves dependencies
│ └─ Finds new dependency: plain-crypto-js@4.2.1
│
├─ Downloads and installs plain-crypto-js@4.2.1
│ └─ Has a "postinstall" script in package.json
│
├─ npm runs postinstall → executes setup.js
│ └─ Two-layer XOR cipher decoding reveals payload
│
├─ setup.js detects your OS
│ ├─ macOS → AppleScript → fetches C++ RAT binary
│ ├─ Windows → VBScript → PowerShell RAT
│ └─ Linux → Shell → Python RAT
│
├─ RAT connects to C2: sfrclak.com:8000
│ └─ Within 1.1 seconds of npm install
│
├─ setup.js self-destructs
│ ├─ Deletes itself
│ ├─ Removes malicious package.json
│ └─ Replaces with clean stub (version 4.2.0)
│
└─ RAT runs in background
└─ Beacons to C2 every 60 seconds
By the time your terminal shows "added 1 package," the RAT is already phoning home and the malicious code has already deleted itself. You'd have no idea anything happened.
1.1 seconds. That's how fast the dropper establishes a C2 connection after npm install. Faster than you can read the install output.
What the Malware Actually Does
They didn't just build one payload and call it a day. They built three. One for each operating system. Each one tailored to its target's quirks, using native tools that wouldn't look suspicious to basic monitoring.
macOS
The dropper uses AppleScript (a legit macOS automation tool) to download a compiled C++ binary from the C2 server. It saves it to /Library/Caches/com.apple.act.mond — a path and name deliberately chosen to look like a real Apple system process. If you saw it in Activity Monitor, you'd probably scroll right past it.
The binary itself is nasty — researchers who tore it apart found it overlaps heavily with WAVESHAPER, a known North Korean backdoor. The internal project name in the binary? macWebT. That name links directly to previous campaigns by BlueNoroff, a North Korean hacking group that's been targeting crypto companies for years.
What can it do once it's running? Read your files, steal your credentials, execute shell commands, and exfiltrate data. Basically, everything. Your machine is theirs.
Windows
On Windows, it gets creative. The dropper finds PowerShell on your system, copies it to %PROGRAMDATA%\wt.exe (renaming it so it doesn't look like PowerShell), writes a VBScript to a temp folder, and uses that to fetch the actual RAT payload from the C2 server.
Then it creates %PROGRAMDATA%\system.bat and registers it as a startup item in the Windows Registry — labeled "Microsoft Update." If you saw that in your startup programs, would you think twice? Probably not. That's the point.
This one survives reboots. And on top of the usual file theft and shell access, it can inject DLLs into running processes and terminate processes that might interfere with it. It's not just a backdoor — it's actively protecting itself.
Linux
The Linux variant is the simplest but arguably the scariest in context. It downloads a Python RAT, saves it to /tmp/ld.py, and runs it with nohup — which means the process survives even after your terminal session ends. It gets reparented to PID 1 (the init process).
Why is this scary? Think about CI/CD. Your Jenkins server, your GitHub Actions runner, your Docker build — if any of these ran npm install during the 3-hour window, the RAT started running alongside your build. And because of the nohup trick, it kept running through your test suite, your deployment, everything. The RAT was sitting on your build server watching your production deploy happen.
The Fake npm Domain
All three variants phone home to the C2 every 60 seconds, but they disguise the traffic as requests to packages.npm.org/product0 (macOS), product1 (Windows), and product2 (Linux).
That domain — packages.npm.org — is not a real npm domain. But it looks like one. If a security analyst glanced at DNS logs and saw traffic going to something that looks like npm infrastructure, they'd probably move on. That's a really clever piece of social engineering embedded in the malware's network layer.
The Self-Destruct Mechanism
This is the part that genuinely impressed me from a technical standpoint (in the most unsettling way possible).
After the RAT launches successfully, setup.js doesn't just sit there waiting to be found. It cleans up after itself:
-
Deletes itself — the malicious
setup.jsfile is gone -
Removes the malicious
package.json— the one with the postinstall hook that triggered everything -
Swaps in a clean replacement — a pre-staged file called
package.mdgets renamed topackage.json, and it reports version4.2.0(the clean decoy from 18 hours earlier)
So after the attack runs, if you go look at node_modules/plain-crypto-js/, everything looks normal. Version 4.2.0. No postinstall script. No setup.js. Your npm audit sees nothing wrong. Your lockfile checker sees nothing wrong.
But the RAT is already running in a background process.
Think about that. The attackers pre-staged a clean decoy version 18 hours in advance specifically so that the self-destruct mechanism would have something innocent to swap in. This was planned with the kind of operational detail you'd expect from a heist movie, not a JavaScript package.
You'd have to dig into npm's raw install logs or compare SHA hashes against the registry to catch it. How many developers do that? How many CI/CD pipelines check for that? Basically zero.
Who Did This
The day after the attack, Google's Threat Intelligence Group pointed the finger at UNC1069 — a North Korean hacking operation that's been on security researchers' radar for years.
How do they know? The evidence is pretty damning:
The macOS binary shares significant code with WAVESHAPER — a backdoor that Mandiant has been tracking and has previously linked to this same North Korean group. The binary even has an internal project name baked in: macWebT, which connects directly to attack tools used in previous campaigns targeting crypto companies.
The C2 infrastructure isn't new either. Some of the domains used in this attack were already flagged in previous North Korean operations — they reused infrastructure, which is how researchers connected the dots.
And the playbook matches perfectly. UNC1069 doesn't hack for espionage or ideology — they hack for money. They target developer machines because developers sit on a goldmine of access: crypto wallets, exchange API keys, cloud credentials, SSH keys to production servers. Compromise one developer's laptop and you potentially have the keys to an entire company's infrastructure.
This is a group that has stolen hundreds of millions in cryptocurrency over the past decade. The Axios attack wasn't random. It was calculated. Developer machines are their ATM, and npm install was the card reader.
The Damage — How Bad Was It
The exposure window was approximately 2 hours and 54 minutes. That sounds short. It's not.
Think about everything that runs npm install on autopilot: CI/CD pipelines spinning up builds, Docker containers pulling fresh dependencies, Dependabot and Renovate creating auto-update PRs, developers onboarding onto new projects, fresh cloud instances bootstrapping. In 3 hours, across a package with 100 million weekly downloads, that's potentially a terrifying number of installations.
And here's the thing that makes the 3-hour window misleading — the RAT doesn't stop when the malicious package gets pulled from npm. Once it's on your machine, it's on your machine. The malicious Axios version is gone from npm, but the RAT that it dropped is still running in the background, still phoning home every 60 seconds, still having full access to your filesystem.
If you got hit, the RAT could have been running silently for days or weeks before you even heard about the attack. Especially with the self-destruct mechanism making the initial infection vector invisible.
What does "fully compromised" actually mean in practice? It means: every credential on that machine — npm tokens, API keys, SSH keys, AWS secrets, database passwords, browser saved passwords — should be assumed stolen. Every file should be assumed read. Every environment variable should be assumed exfiltrated. That's not paranoia. That's what a RAT with full shell access and file system enumeration can do in minutes.
How It Was Caught
Credit where it's due — this was caught fast by internet standards.
The first domino was automated tooling. Security scanners noticed a brand-new, unknown dependency (plain-crypto-js) suddenly appearing in a massively popular package. That's a red flag that most modern supply chain monitoring tools are trained to catch.
StepSecurity's Harden-Runner tool deserves a specific shoutout here. It was running in a GitHub Actions environment, monitoring outbound network connections during CI builds. When npm install triggered the payload and the dropper tried to call home to sfrclak.com, Harden-Runner caught it in real time — recording the C2 connection happening within 1.1 seconds of install. That's the kind of visibility that caught this before it could spread further.
Community reports started flooding in to npm's security team, who pulled both malicious versions approximately 2 hours and 54 minutes after publication. Fast response, all things considered. But at the scale Axios operates at, even fast isn't fast enough to prevent significant damage.
After the public disclosure, the C2 infrastructure at sfrclak.com went dark. The attackers pulled the plug on their end — they knew the party was over.
Am I Affected? How to Check
Before you panic, here's how to actually figure out if you were hit. Run these and breathe.
Check your lockfile for the bad versions:
grep -r "axios@1.14.1\|axios@0.30.4" package-lock.json yarn.lock pnpm-lock.yaml
# Also check for the phantom dependency
grep -r "plain-crypto-js" package-lock.json yarn.lock pnpm-lock.yaml node_modules/
If those come back empty, you're almost certainly fine. The lockfile is your source of truth.
If you do find a match, check for RAT artifacts:
# macOS — the disguised binary
ls -la /Library/Caches/com.apple.act.mond
# Linux — the Python RAT
ls -la /tmp/ld.py
# Windows (PowerShell)
Test-Path "$env:PROGRAMDATA\wt.exe"
Test-Path "$env:PROGRAMDATA\system.bat"
Check if your machine was talking to the C2:
grep -r "sfrclak.com\|callnrwise.com\|calltan.com" /var/log/
netstat -an | grep "142.11.206.73"
For the forensically inclined, here are the SHA hashes of the malicious packages — you can compare these against your npm cache to be absolutely sure:
axios@1.14.1: 2553649f2322049666871cea80a5d0d6adc700ca
axios@0.30.4: d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71
plain-crypto-js@4.2.1: 07d889e2dadce6f3910dcbc253317d28ca61c766
If You Find Anything
Don't just delete the malware and move on. If any of those checks came back positive:
- Treat the machine as fully compromised. Not "probably fine." Fully compromised. The RAT had shell access and file system enumeration — assume everything on that machine was accessible.
- Rotate every credential that machine has ever touched. npm tokens, API keys, SSH keys, cloud secrets, database passwords. All of them. Yes, all of them.
- Hunt for persistence. On Windows, check the Registry Run keys for anything labeled "Microsoft Update" — that's the RAT's disguise. On macOS, check for the binary at the path above. On Linux, check for orphaned Python processes.
- Rebuild from scratch. Seriously. Don't try to clean the machine. You don't know what else was modified. Wipe and rebuild.
- Audit your CI/CD. If any pipeline ran during that 3-hour window, every artifact it produced is suspect. Deployments, Docker images, binaries — all of it.
-
Block the C2 domains at the network level:
sfrclak.com,callnrwise.com,calltan.com, and the IP142.11.206.73.
The Bigger Picture — npm's Trust Problem
Let's zoom out for a second. Because this isn't really about Axios. Axios was just the vehicle.
The real problem is something uncomfortable: npm install is essentially curl | bash with extra steps. Every time you run it, you're downloading code from the internet, automatically executing arbitrary scripts (postinstall hooks), with whatever permissions your user has, trusting that thousands of transitive dependencies haven't been tampered with.
We all know this. We've known it for years. And we keep doing it anyway because the alternative — auditing every dependency and every transitive dependency — is impractical for any real project.
That trust model broke on March 31, 2026. Again.
What npm got right: They removed the malicious versions in under 3 hours. That's fast. And OIDC Trusted Publishing exists and actually works — when it's configured correctly and classic tokens are revoked.
What's still broken — and this is the part that frustrates me:
Classic npm tokens still exist side-by-side with OIDC, and npm prioritizes the classic token when both are present. There's no way to say "OIDC only, reject all classic token publishes" at the registry level. It's like having a biometric lock on your front door but also leaving a spare key under the mat — and the key overrides the biometric.
Postinstall scripts still run by default. Most developers haven't added ignore-scripts=true to their .npmrc because it breaks half their toolchain. So every npm install is a gamble that none of your hundreds of transitive dependencies have decided to run malicious code today.
There's still no cryptographic verification that what's on npm matches what's on GitHub. The malicious Axios versions had no corresponding git commit, no release tag, nothing in the actual repository. The npm registry and the GitHub repo are two completely separate systems with no enforced link between them.
And for a package downloaded 100 million times a week? No mandatory 2FA. npm recommends it. Doesn't enforce it. A recommendation is not a security measure.
What Developers Should Do Now
If you're not affected, great. But don't just move on and forget about this. Here's what to actually change:
Right now:
# Check your Axios version
npm ls axios
# If you're on 1.14.1 or 0.30.4 (unlikely at this point, but check)
npm install axios@1.14.0
# Remove the phantom dependency if it somehow stuck around
rm -rf node_modules/plain-crypto-js
# Run an audit
npm audit
Going forward — and I know some of these are annoying, but they matter now:
Use exact versions, not ranges. Add save-exact=true to your .npmrc. Version ranges (^1.14.0) are what let the malicious version auto-install. Exact versions mean you explicitly choose when to update, and you can review the diff.
Start reviewing lockfile changes in PRs. When a PR shows a new dependency appearing in your lockfile that nobody explicitly added — that's a red flag. Most teams skip lockfile diffs because they're noisy. After Axios, that noise might save you.
Disable postinstall scripts. Add ignore-scripts=true to .npmrc. Yes, this will break some packages that legitimately need postinstall hooks. You'll have to whitelist those explicitly. It's annoying. It's also the single most effective defense against this exact attack vector.
Use npm ci in CI/CD, not npm install. npm ci installs exactly what's in the lockfile — no resolution, no surprises. npm install can resolve to newer versions, which is exactly how the malicious version got pulled in.
Kill your classic npm tokens. If you have OIDC Trusted Publishing set up, revoke every classic token. Don't leave both active. The Axios attack literally happened because a classic token existed alongside OIDC. Don't be the next lesson.
Monitor outbound connections from your builds. Tools like StepSecurity Harden-Runner, Socket.dev, and Snyk can flag suspicious network activity during CI/CD. If your build is suddenly calling sfrclak.com — you want to know about that in real time, not in a blog post two weeks later.
What This Attack Teaches Us
This isn't just an Axios problem. It's a mirror held up to how the entire JavaScript ecosystem works.
The entry point was embarrassingly simple. Not a zero-day. Not a sophisticated exploit. One stolen npm token. That single token bypassed OIDC, bypassed CI/CD, bypassed code review — everything. All the security infrastructure in the world means nothing if there's a classic token sitting in a compromised .env file somewhere.
The payload was anything but simple. Three OS-specific RATs. A self-destruct mechanism with a pre-staged decoy. C2 domains disguised as npm infrastructure. 18 hours of advance staging. This is the asymmetry of supply chain attacks — trivial to get in, devastating once inside.
3 hours was both impressive and terrifying. npm's security team responded fast. But at the scale of 100 million weekly downloads, "fast" still means potentially thousands of compromised installations. There's no response time that's fast enough when the attack surface is this big.
We keep learning the same lesson. event-stream in 2018 taught us that trust in maintainers is fragile. ua-parser-js in 2021 taught us that account security matters. xz-utils in 2024 taught us that social engineering can play the long game. And now Axios in 2026 is teaching us that classic tokens are ticking time bombs — and that OIDC only works if you actually commit to it.
The JavaScript ecosystem runs on trust. And that trust is held together by npm tokens, maintainer email addresses, and the assumption that no one's postinstall script is going to ruin your day. That's not a security model. That's a hope model.
The Axios attack won't be the last. But maybe — maybe — it'll be the one that makes us stop treating supply chain security as someone else's problem.
Want to talk about supply chain security, npm hardening, or just vent about the state of open source? Let's connect.
Top comments (0)