Last updated: April 2026
Key Takeaways
- An attacker hijacked the npm account of the lead axios maintainer and published two poisoned versions (1.14.1 and 0.30.4) that silently installed a cross-platform remote access trojan via a postinstall hook. The malicious versions were live for approximately three hours on March 31, 2026.
- Anyone who ran
npm installin any Node.js project during the exposure window is potentially affected — including CI/CD pipelines, developer workstations, and home automation or local AI setups that use npm packages. - If you are affected, assume full system compromise. Check for the
plain-crypto-jsdirectory in yournode_modules, downgrade to axios 1.14.0 or 0.30.3, isolate the machine, and rotate every credential immediately.
What Happened: The Axios Compromise in Plain Language
Axios is the most popular JavaScript HTTP client library in the world, with over 100 million weekly downloads on npm. It is embedded in web applications, backend APIs, mobile apps, serverless functions, CI/CD pipelines, and developer workstations across virtually every industry. If your organization uses any Node.js-based software, there is a high probability that axios is somewhere in your dependency tree.
On March 30–31, 2026, an attacker compromised the npm account of Jason Saayman, the lead maintainer of the axios project, and used it to publish two poisoned releases: axios@1.14.1 (targeting the modern 1.x user base) and axios@0.30.4 (targeting the legacy 0.x branch). Both versions appeared completely legitimate in the npm registry — they were published under the real maintainer's account name, and every source file in the package was identical to the previous clean release.
The only change was a single line added to package.json: a new dependency called plain-crypto-js. This package was never imported or referenced anywhere in the axios source code. It existed for one purpose: to run a postinstall script that deployed a cross-platform remote access trojan targeting macOS, Windows, and Linux. Within two seconds of npm install, the malware was contacting the attacker's command-and-control server — before npm had even finished resolving the rest of the dependency tree.
The malicious versions were live for approximately three hours before npm removed them and placed a security hold on plain-crypto-js. Three hours is not a long window, but for a package with 100 million weekly downloads, it is more than enough. Security firm Snyk confirmed that any system which installed the compromised versions during that window should be treated as fully compromised.
Who Is Behind It
On April 1, 2026, Google Threat Intelligence Group (GTIG) publicly attributed the axios compromise to UNC1069, a financially motivated threat actor linked to North Korea that has been active since at least 2018. The attribution is based on infrastructure overlaps and the use of WAVESHAPER.V2, an updated version of a backdoor previously deployed by the same group. GTIG assigned the obfuscated dropper the tracking name SILKBELL and the platform-specific payloads the name WAVESHAPER.V2.
This is a notable escalation. Targeting the single most-downloaded npm package with a pre-staged, multi-platform RAT operation represents a level of operational sophistication that goes well beyond opportunistic attacks on obscure packages. The attacker pre-staged the malicious dependency 18 hours in advance, built separate payloads for three operating systems, poisoned both release branches within 39 minutes, and designed every artifact to self-destruct after execution.
GTIG noted that the axios attack is separate from the TeamPCP supply chain campaign that we covered in our earlier reporting on the LiteLLM compromise and the Cisco data breach via Trivy. However, the broader pattern is unmistakable: open-source supply chain attacks are escalating in frequency, sophistication, and impact — and state-level actors are now participating alongside financially motivated groups.
How the Attack Worked
Account Hijack
The attacker compromised the jasonsaayman npm account, the primary maintainer of axios. The account's registered email was changed to an attacker-controlled ProtonMail address (ifstap@proton.me), locking out the legitimate owner. Using this access, the attacker published malicious builds to both the 1.x and 0.x release branches.
A critical forensic detail: every legitimate axios 1.x release is published through GitHub Actions using npm's OIDC Trusted Publisher mechanism, which cryptographically ties each publish to a verified GitHub workflow. The malicious axios@1.14.1 broke that pattern entirely — it was published manually via a stolen npm access token with no OIDC binding, no gitHead, and no corresponding commit or tag in the axios GitHub repository. The release existed only on npm. The attacker must have obtained a long-lived classic npm access token for the account, since the OIDC tokens used by GitHub Actions are ephemeral and cannot be stolen.
Pre-Staged Malicious Dependency
Before publishing the poisoned axios versions, the attacker created a throwaway npm account (nrwise@proton.me) and used it to publish a package called plain-crypto-js. This was a near-exact copy of the legitimate crypto-js library — all 56 source files were bit-for-bit identical — with the real author's name and GitHub repository URL listed in the metadata to appear legitimate.
Version 4.2.0 was published first as a clean decoy, 18 hours before the attack, to give the package a brief publishing history and avoid "brand-new package" alarms from security scanners. Version 4.2.1 followed with the malicious payload: a single postinstall entry in package.json pointing to an obfuscated dropper called setup.js.
The Postinstall Hook
The entire weapon was one line in package.json:
"postinstall": "node setup.js"
When npm installs a package, it automatically executes any postinstall script defined in that package's configuration. No user interaction is required. No prompt is shown. The script runs silently in the background during npm install.
The setup.js dropper used two layers of obfuscation — XOR cipher and modified base64 encoding — to hide its strings from static analysis tools. Once decoded, the script detected the host operating system, contacted a command-and-control server at http://sfrclak.com:8000/6202033, downloaded a platform-specific RAT payload, launched it as a detached background process, and then cleaned up all evidence of its own existence.
The entire process, from npm install to C2 contact, took approximately two seconds. The RAT payload continued running as a background process even after the npm install completed.
Platform-Specific Payloads
The dropper delivered three different payloads depending on the target operating system:
| Platform | Delivery Method | RAT Location | Persistence |
|---|---|---|---|
| macOS | AppleScript executed via osascript
|
/Library/Caches/com.apple.act.mond (mimics Apple system process) |
Binary runs in background via /bin/zsh
|
| Windows | VBScript → hidden PowerShell |
%TEMP%\6202033.ps1 (self-deletes after execution) |
%PROGRAMDATA%\wt.exe — a persistent copy of PowerShell disguised as Windows Terminal that survives reinstalls and reboots |
| Linux | curl → Python script via nohup
|
/tmp/ld.py |
Detached process re-parented to PID 1 (init) |
Each platform sent a distinct POST body to the C2 server with a string designed to look like legitimate npm registry traffic (packages.npm.org/product0, product1, product2) to evade network monitoring. The Linux payload also covered FreeBSD, Android (Node.js on Termux), and container environments — anything that was not macOS or Windows fell through to the Linux branch.
Self-Cleanup and Anti-Forensics
After launching the RAT, the dropper performed three cleanup steps in sequence: it deleted setup.js (the dropper itself), deleted the malicious package.json, and renamed a pre-staged clean stub file (package.md) to package.json. The replacement stub reported version 4.2.0 — not 4.2.1 — so that anyone running npm list after infection would see a version number that did not match the malicious version they were told to look for.
Post-infection, npm audit reveals nothing. The node_modules/plain-crypto-js/package.json file looks clean. The dropper is gone. The only surviving evidence is the existence of the plain-crypto-js/ directory itself — a package that has never appeared in any legitimate axios release.
How to Check If You Are Affected
Indicators of Compromise
| Indicator Type | Value | Notes |
|---|---|---|
| Malicious npm package |
axios@1.14.1 (sha: 2553649f) |
Removed from npm registry |
| Malicious npm package |
axios@0.30.4 (sha: d6f3f62f) |
Removed from npm registry |
| Malicious npm package |
plain-crypto-js@4.2.1 (sha: 07d889e2) |
Security hold applied by npm |
| C2 domain | sfrclak.com |
Block in firewall and DNS |
| C2 IP address | 142.11.206.73 |
Port 8000 |
| macOS artifact | /Library/Caches/com.apple.act.mond |
RAT binary disguised as Apple cache daemon |
| Windows artifact (persistent) | %PROGRAMDATA%\wt.exe |
Copy of PowerShell disguised as Windows Terminal — survives reinstalls |
| Linux artifact | /tmp/ld.py |
Python RAT script |
| Attacker account (hijacked) |
jasonsaayman (email changed to ifstap@proton.me) |
Legitimate axios maintainer — account compromised |
| Attacker account (created) |
nrwise (nrwise@proton.me) |
Published plain-crypto-js
|
Check Your Code Repositories
Search your projects for any reference to the compromised versions in lockfiles or for the presence of the malicious dependency directory:
# Check for compromised axios versions in any project
npm list axios 2>/dev/null | grep -E "1\.14\.1|0\.30\.4"
# Search lockfiles
grep -A1 '"axios"' package-lock.json | grep -E "1\.14\.1|0\.30\.4"
# Check for the malicious dependency directory
ls node_modules/plain-crypto-js 2>/dev/null && echo "POTENTIALLY AFFECTED"
If the plain-crypto-js directory exists in your node_modules, the dropper ran — regardless of what version number the package.json inside it reports. This package is not a dependency of any legitimate axios release.
If your organization uses GitHub, you can search across all repositories using GitHub code search with queries like org:YOUR_ORG "plain-crypto-js" path:package-lock.json to quickly determine your blast radius.
Check CI/CD Pipelines
Review pipeline logs for any npm install or npm ci runs that executed between 00:21 and 03:15 UTC on March 31, 2026. Search for axios@1.14.1, axios@0.30.4, or plain-crypto-js in install output. If you have network monitoring on your CI runners, look for outbound connections to sfrclak.com or 142.11.206.73 on port 8000.
Any pipeline that installed either malicious version should be treated as compromised. If you use ephemeral runners (like GitHub-hosted runners), the runner environment itself is destroyed after each job, but all secrets and credentials that were available to the workflow during the compromised run should be rotated immediately.
Check Developer and Home Lab Machines
Search your entire home directory for any trace of the malicious package across all projects:
# Search for the malicious dependency across all projects
find ~ -type d -name "plain-crypto-js" -path "*/node_modules/*" 2>/dev/null
# Search all lockfiles for compromised versions
find ~ -name "package-lock.json" -exec grep -l "axios.*1\.14\.1\|axios.*0\.30\.4" {} \; 2>/dev/null
Then check for RAT artifacts specific to your operating system:
# macOS
ls -la /Library/Caches/com.apple.act.mond 2>/dev/null && echo "COMPROMISED"
# Linux
ls -la /tmp/ld.py 2>/dev/null && echo "COMPROMISED"
# Windows (run in cmd.exe)
dir "%PROGRAMDATA%\wt.exe" 2>nul && echo COMPROMISED
If you find any of these artifacts, or if network logs show outbound connections to sfrclak.com or 142.11.206.73, the RAT successfully executed on your machine.
What to Do If You Are Affected
Step 1: Isolate the machine from the network immediately. Disconnect from Wi-Fi, unplug Ethernet, and disable any VPN connections to prevent further data exfiltration or lateral movement.
Step 2: Inventory all secrets and credentials on the machine before reformatting. This includes npm tokens (~/.npmrc), SSH private keys (~/.ssh/), AWS credentials, GCP and Azure tokens, Git credentials and personal access tokens, Docker registry credentials, Kubernetes configs, API keys in local .env files, browser-stored passwords, and database connection strings. You need a complete list so you can rotate everything after the wipe.
Step 3: Reformat and rebuild from a known-good state. Do not attempt to clean a compromised machine in place. The RAT had arbitrary code execution capability and may have installed additional persistence mechanisms beyond the known indicators.
Step 4: Rotate every credential from a separate, known-clean machine. Revoke and reissue — do not rotate in place. Review access logs for anomalous activity during and after the compromise window.
Step 5: Downgrade axios to a safe version and lock it.
# Downgrade to safe versions
npm install axios@1.14.0 # for 1.x users
npm install axios@0.30.3 # for 0.x users
# Add overrides to package.json to prevent resolution back to malicious versions
{
"dependencies": { "axios": "1.14.0" },
"overrides": { "axios": "1.14.0" },
"resolutions": { "axios": "1.14.0" }
}
Step 6: Block C2 traffic. Add blocklist entries across your network:
# Block via /etc/hosts (macOS/Linux)
echo "0.0.0.0 sfrclak.com" >> /etc/hosts
# Block via firewall (Linux)
iptables -A OUTPUT -d 142.11.206.73 -j DROP
If you run Pi-hole on your home network, add sfrclak.com to your blocklist as an additional safety layer.
Why This Matters Beyond the Developer Community
The axios compromise is the latest and most high-profile incident in an escalating wave of open-source supply chain attacks in 2026. It follows a pattern that ModemGuides readers will recognize from our coverage of the LiteLLM supply chain attack and the Cisco data breach via the Trivy supply chain compromise: attackers are targeting widely trusted open-source packages to reach thousands of downstream users through a single point of compromise.
What makes the axios attack particularly concerning is the scale of the blast radius. Axios is present in an estimated 80% of cloud and code environments. It is a transitive dependency in thousands of other npm packages, meaning your project may depend on it even if you never explicitly installed it. Researchers at Wiz observed execution of the malicious package in 3% of affected environments — a staggering number given the installed base.
This is not only a concern for professional developers. If you run any Node.js-based home automation or local AI infrastructure — Home Assistant integrations, n8n workflows, MCP servers, OpenClaw skills, or custom automation scripts — npm packages are part of your environment. The postinstall hook attack vector fires on npm install with zero user interaction. No confirmation prompt, no warning, no visible output. If your home lab pulled a fresh npm install during the three-hour window, the RAT ran on your machine.
The deeper lesson here is that every dependency you pull from a remote package registry is an implicit trust decision — and the npm ecosystem makes millions of these decisions automatically, by default, with no verification. The dependency-trust model itself is the vulnerability. Pinned versions, lockfiles, and cooldown periods exist to mitigate this, but most projects do not use them. The axios attack shows what happens when that implicit trust is violated at the top of the dependency tree.
How to Protect Yourself Going Forward
Pin Your Dependencies
Never allow floating version ranges like ^1.14.0 or ~1.14.0 in production environments. Use exact version pins (e.g., "axios": "1.14.0") and commit your package-lock.json to version control. In CI/CD pipelines, use npm ci (not npm install) — it respects the lockfile exactly and will fail if the lockfile is out of sync with package.json, which is the behavior you want in automated environments.
Use Release-Age Gating
All major Node.js package managers now support some form of release-age policy that blocks newly published packages for a configurable cooldown period. Since most malicious packages are identified and removed within 24–72 hours of publication, a 3–7 day cooldown window would have blocked the axios attack entirely. Here is how to configure it across each package manager:
# npm (v11.10+) — add to .npmrc
min-release-age=7d
# pnpm (v10.16+) — add to .npmrc
minimum-release-age=7d
minimum-release-age-exclude=esbuild,turbo
# Yarn (v4.10+) — add to .yarnrc.yml
npmMinimalAgeGate: "7d"
# Bun (v1.3+) — add to bunfig.toml
[install]
minimumReleaseAge = 604800
This is one of the single most effective defenses against supply chain attacks. It is simple to configure and costs nothing except a brief delay before adopting new versions — a delay that gives the security community time to flag malicious packages before they reach your machine.
Disable Postinstall Scripts
The entire axios attack relied on a postinstall script executing during npm install. You can disable this attack surface entirely:
# For CI/CD pipelines
npm ci --ignore-scripts
# For local development (global setting)
npm config set ignore-scripts true
Some packages legitimately use postinstall scripts for native compilation or setup. If you disable scripts globally, you may need to selectively run them for specific packages. But in CI/CD environments where you are building and deploying code, --ignore-scripts should be the default.
Monitor Your Network
If you are running a home lab or local AI stack, DNS-level monitoring provides passive visibility into unexpected outbound connections. Pi-hole gives you a dashboard showing every DNS query on your network. If a compromised package starts making C2 callbacks to unfamiliar domains, you will see it in the query log — activity that would be completely invisible on a stock network setup.
For more comprehensive protection, a pfSense firewall or open-source router firmware like OpenWrt gives you the ability to segment your network into isolated VLANs. Keep your development machines, IoT devices, and local AI servers on separate network segments. If a supply chain attack compromises one segment, proper isolation prevents lateral movement to the rest of your network.
Minimize Your Dependency Surface
Every npm package you install is a trust decision you are making on behalf of every machine in your environment. Audit your dependency trees regularly. Remove packages you no longer use. For home lab and local AI setups, prefer tools with minimal external dependencies — running models locally through Ollama on dedicated hardware eliminates entire categories of supply chain risk compared to routing workloads through cloud gateways that depend on dozens of third-party packages.
The axios attack is a reminder that the convenience of npm install comes with a cost: you are placing implicit trust in every maintainer account, every dependency, and every postinstall script in your entire dependency tree. That trust was violated at the highest level of the npm ecosystem. The tools to protect yourself exist — pinned versions, lockfiles, cooldown periods, disabled scripts, network monitoring. Use them.
Frequently Asked Questions
Was the actual axios source code modified?
No. Every source file in the malicious axios releases was bit-for-bit identical to the previous clean versions. The only change was a single line added to package.json — a new dependency called plain-crypto-js that was never imported or referenced in the axios codebase. The entire attack was delivered through the dependency's postinstall hook, not through any modification to axios itself.
How long were the malicious versions available on npm?
axios@1.14.1 was published at 00:21 UTC and removed at approximately 03:15 UTC on March 31, 2026 — a window of roughly 2 hours and 53 minutes. axios@0.30.4 was published at 01:00 UTC and removed at approximately the same time, giving it a window of about 2 hours and 15 minutes. The malicious dependency plain-crypto-js@4.2.1 was live for approximately 4 hours and 27 minutes before npm placed a security hold on it.
Does running npm audit detect this compromise?
No. The dropper's self-cleanup replaces the malicious package.json with a clean stub after execution, so npm audit finds nothing. The reliable check is the existence of the node_modules/plain-crypto-js/ directory itself. This package has never appeared in any legitimate axios release — if the directory exists, the dropper ran.
I use axios but did not update during the three-hour window. Am I safe?
If your package-lock.json pins axios to any version other than 1.14.1 or 0.30.4, and you did not run a fresh npm install (without a lockfile, or with a floating version range) between 00:21 and 03:15 UTC on March 31, you are almost certainly safe. The malicious versions had to be actively downloaded and installed during the window to trigger the postinstall hook. Verify by checking your lockfile and searching for the plain-crypto-js directory to be certain.
Could this affect my Home Assistant, n8n, or local AI setup?
Yes, if those environments use npm and pulled dependencies during the exposure window. Home Assistant itself is Python-based, but many custom integrations, Node-RED flows, n8n workflows, and MCP server configurations use Node.js packages. If any of those environments ran npm install during the three-hour window, they could have pulled the compromised axios version as a direct or transitive dependency. Check for the plain-crypto-js directory in every node_modules folder on the affected machine.
Who was behind the attack?
Google Threat Intelligence Group (GTIG) attributed the axios compromise to UNC1069, a financially motivated threat actor linked to North Korea. The attribution is based on infrastructure overlaps and the use of WAVESHAPER.V2, an updated version of a backdoor previously associated with the group. GTIG noted that this attack is separate from the TeamPCP campaign responsible for the Trivy, LiteLLM, and Cisco supply chain compromises, though both represent the same broader trend of escalating open-source supply chain attacks.
What is the safest axios version to use right now?
axios@1.14.0 is the safe version for users on the 1.x branch. axios@0.30.3 is the safe version for users on the legacy 0.x branch. Both versions are the last clean releases before the compromised versions were published. Pin to these exact versions in your package.json and add overrides and resolutions entries to prevent transitive dependencies from resolving to the malicious versions.

