Executive Summary
On March 31, 2026, an attacker hijacked the npm account of Axios’s primary maintainer and published two malicious versions of the most popular HTTP client library in the JavaScript ecosystem. The backdoored packages—axios@1.14.1 and axios@0.30.4—injected a trojanized dependency that delivered cross-platform remote access trojans to macOS, Windows, and Linux machines within seconds of installation. With approximately 100 million weekly downloads and a presence in 80% of cloud environments, Axios represents one of the highest-value targets in the npm registry. The malicious versions were live for approximately 2.5–3 hours, with observed execution in 3% of affected environments. Security researchers have characterized the operation as espionage-grade APT activity. This article provides a complete technical breakdown of the attack chain, the malware mechanics, and the remediation path.
The attack was meticulously staged over approximately 22 hours, demonstrating significant operational planning:
| Timestamp (UTC) | Event | Significance |
|---|---|---|
| March 30, 05:57 | plain-crypto-js@4.2.0 published by nrwise@proton.me |
Clean decoy establishing npm publishing history; 56 source files bit-for-bit identical to legitimate crypto-js@4.2.0 |
| March 30, 23:59 | plain-crypto-js@4.2.1 published with malicious postinstall hook |
Three file-level differences from 4.2.0: added postinstall script to package.json, added setup.js dropper (4.2 KB), added clean package.md stub |
| March 31, 00:21 | axios@1.14.1 published via compromised jasonsaayman account |
Published manually (no trusted publisher, no gitHead, no corresponding GitHub tag); email changed to ifstap@proton.me |
| March 31, 01:00 | axios@0.30.4 published |
Second release branch poisoned 39 minutes later, ensuring coverage of both 0.x and 1.x dependency ranges |
| March 31, ~03:15 | npm unpublishes both malicious Axios versions | Approximately 2 hours 53 minutes (1.14.1) and 2 hours 15 minutes (0.30.4) of exposure |
| March 31, 03:25 | npm initiates security hold on plain-crypto-js |
Malicious dependency quarantined |
| March 31, 04:26 | npm publishes security-holder stub for plain-crypto-js |
Prevents reinstallation from cached registries |
Key operational detail: The 18-hour gap between publishing the clean decoy (plain-crypto-js@4.2.0) and the malicious version (4.2.1) was deliberate. This established a non-zero publishing history, avoiding automated alerts that flag packages with no prior versions. The attacker also pre-built three platform-specific RAT payloads—macOS, Windows, Linux—before the attack began.
The attacker gained control of the jasonsaayman npm account—Axios’s lead maintainer, who holds the highest permissions for both the npm package and the GitHub repository. The account’s registered email was changed to ifstap@proton.me, an attacker-controlled address, effectively locking out the legitimate owner from password recovery.
The specific credential compromise method has not been publicly disclosed. However, several forensic indicators confirm the hijack:
Axios collaborator DigitalBrainJS documented the coordination challenge in the GitHub issue: because the compromised account held the highest permissions, other collaborators could not revoke its access. npm support had to intervene directly.
plain-crypto-js@4.2.1 was added as a runtime dependency in the malicious Axios versions. It was never imported anywhere in the Axios source code. The 56 cryptographic source files were bit-for-bit identical to the legitimate crypto-js@4.2.0 library—pure camouflage. The entire purpose of the package was to execute its postinstall hook.
This is a critical distinction: the attack vector was npm’s automatic lifecycle script execution, not any vulnerability in the Axios library code itself.
The 4.2 KB setup.js file used a two-layer obfuscation scheme:
_trans_1(x, r): XOR cipher using key “OrDeR_7077” (effective key bytes: [0,0,0,0,0,0,7,0,7,7])_trans_2(x, r): String reversal, underscore-to-equals substitution, base64 decode, then XOR decipherSensitive strings were stored in an stq[] array and decoded at runtime. The decoded C2 URL was http://sfrclak.com:8000/6202033, where 6202033 served as a campaign identifier.
The entry point function wrapped the entire attack in a top-level try/catch that swallowed all errors silently. Regardless of whether the C2 server was reachable, the dropper completed, npm install exited with code 0, and the build continued normally. This made detection through build failures impossible.
/tmp/6202033nohup osascript (detached from parent process)/Library/Caches/com.apple.act.mond (mimics Apple system cache naming)codesignpackages.npm.org/product0where powershell%TEMP%\6202033.ps1-WindowStyle Hidden -ExecutionPolicy Bypass%PROGRAMDATA%\wt.exe (disguised as Windows Terminal)MicrosoftUpdate with batch file re-download mechanism%TEMP%\6202033.vbs executed via cscript //nologopackages.npm.org/product1wt.exe remains/tmp/ld.pynohup python3 /tmp/ld.py with PID detached to init (ppid: 1)packages.npm.org/product2Despite platform-specific delivery, all three variants provided consistent capabilities:
sfrclak.com:8000 every 60 secondsAfter executing the dropper, setup.js performed three sequential cleanup operations:
fs.unlink(__filename)—Deletes the setup.js dropper from node_modules/plain-crypto-js/fs.unlink("package.json")—Removes the malicious manifest containing the postinstall hookfs.rename("package.md", "package.json")—Replaces with a pre-staged clean stub reporting version “4.2.0” (not 4.2.1)This triple-cleanup creates a secondary deception layer: after infection, npm list shows a version mismatch between the resolved version (4.2.1) and the on-disk manifest (4.2.0), potentially confusing incident responders. The key detection signal: if node_modules/plain-crypto-js exists at all, the dropper ran—this package never appeared in any legitimate Axios version.
Any system that installed axios@1.14.1 or axios@0.30.4 during the exposure window should be treated as fully compromised. This includes:
npm install or npm updateAxios is a transitive dependency of thousands of npm packages. Organizations may have pulled the compromised versions without any developer explicitly running npm install axios. Any package that specifies Axios with a caret or tilde version range (^1.14.0 or ~1.14.0) could have resolved to 1.14.1 during the exposure window.
Docker images, npm cache directories, and CI/CD artifact stores may retain the malicious packages even after npm removal. Organizations should audit:
find ~/.npm/_cacache -name "*.tgz" | xargs tar -tf 2>/dev/null | grep plain-crypto-jsAs of this writing, no formal attribution has been made. The Axios attack has not been linked to TeamPCP, the group behind the Trivy, Checkmarx, LiteLLM, and Telnyx compromises earlier in March 2026.
However, security researchers from multiple firms (Wiz, StepSecurity, Snyk, SANS) have noted characteristics consistent with state-sponsored or espionage-focused operations:
Whether attributed to a known group or independent, the attack represents a new threshold in npm supply chain sophistication.
| Package | SHA1 |
|---|---|
axios@1.14.1 |
2553649f232204966871cea80a5d0d6adc700ca |
axios@0.30.4 |
d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71 |
plain-crypto-js@4.2.1 |
07d889e2dadce6f3910dcbc253317d28ca61c766 |
sfrclak.comcallnrwise.com142.11.206.73/6202033packages.npm.org/product0 (macOS), product1 (Windows), product2 (Linux)/Library/Caches/com.apple.act.mond%PROGRAMDATA%\wt.exe%TEMP%\6202033.vbs, %TEMP%\6202033.ps1/tmp/ld.py/tmp/6202033node_modules/plain-crypto-js/ (should never exist in legitimate Axios installs)jasonsaayman (hijacked; email changed to ifstap@proton.me)nrwise (attacker-created; email nrwise@proton.me)# Check installed Axios version
npm list axios
# Search for malicious dependency (its presence = dropper executed)
ls node_modules/plain-crypto-js 2>/dev/null && echo "COMPROMISED" || echo "Clean"
# Check for RAT artifacts
# macOS:
ls -la /Library/Caches/com.apple.act.mond 2>/dev/null
# Linux:
ls -la /tmp/ld.py 2>/dev/null
# Windows (PowerShell):
# Test-Path "$env:PROGRAMDATA\wt.exe"
# Check network logs for C2 traffic
# grep for sfrclak.com or 142.11.206.73 in proxy/firewall/DNS logs
rm -rf node_modules/plain-crypto-js && npm install --ignore-scriptsnpm cache clean --force142.11.206.73, DNS sinkhole for sfrclak.com and callnrwise.comIf any evidence of compromise is found, rotate all credentials accessible from the affected machine:
.env files"axios": "1.14.0" (no caret, no tilde)"overrides": { "axios": "1.14.0" } in package.jsonnpm ci --ignore-scriptsmin-release-age=7 to .npmrcfetch() eliminates the Axios dependency for many use casesplain-crypto-js: Add to package blocklist in your registry proxyThe Axios compromise is a watershed moment for npm security. It demonstrates that:
The JavaScript ecosystem’s reliance on deep dependency trees, automatic script execution, and single-maintainer publishing authority creates an attack surface that no single mitigation can close. Defense in depth—version pinning, script disabling, release age delays, egress filtering, and server-side credential isolation—is the only viable path forward.
The compromised versions (1.14.1 and 0.30.4) have been removed from npm. Safe versions include 1.14.0 and 0.30.3. However, you should verify your installed version, purge npm caches, and pin to a specific known-safe version. The underlying Axios library code was never modified—the attack was entirely through an injected dependency.
StepSecurity identified the compromised versions on March 30, 2026, through anomalous network traffic during instrumented package installation. The dropper’s C2 communication (outbound HTTP to sfrclak.com:8000) was flagged by behavioral analysis within their Harden-Runner tool. Community researchers then confirmed the malware’s presence and documented the full attack chain within hours.
Many enterprise applications still depend on Axios 0.x due to breaking changes in the 1.x release. By poisoning both branches within 39 minutes, the attacker maximized coverage across the entire Axios user base. Organizations pinned to ^0.30.0 would have resolved to 0.30.4, while those on ^1.14.0 would have received 1.14.1.
The RAT targets the machine where npm install runs, not the machine where the built application executes. For frontend applications, the primary risk is to developer workstations and CI/CD servers that build the application. The malicious postinstall script runs during installation, not at runtime in the browser. However, if a developer’s machine was compromised, the RAT could access source code, environment files, and deployment credentials.
Modern Node.js (v18+) includes native fetch() support built on Undici. For straightforward HTTP requests, native fetch eliminates the Axios dependency entirely. However, Axios provides features not available in native fetch: request and response interceptors, upload progress events, automatic JSON serialization, timeout configuration, and request cancellation via AbortController integration. Evaluate whether your use case requires these features before migrating.
A configuration option that tells your package manager to refuse packages published less than N days ago. Supported across major package managers: npm (min-release-age=7 in .npmrc), pnpm (minimum-release-age=10080 in minutes), bun (minimumReleaseAge = 604800 in seconds), and yarn berry (npmMinimalAgeGate: "3d"). This creates a detection buffer: if a compromised version is published and discovered within the delay window, it never reaches your environment. The Axios malicious versions were live for under 3 hours—a 7-day release age gate would have prevented all exposure.