On March 31, 2026, two malicious versions of axios were published to npm: axios@1.14.1 and axios@0.30.4. Both were live for roughly three hours before npm pulled them down. During that window, anyone who ran npm install axios could have had a Remote Access Trojan (RAT) dropped silently on their machine or CI runner, with no errors and no warnings.
This post breaks down what happened, how the attack worked, and the exact commands to check if you were affected.
What happened
The attacker compromised the npm account of the primary axios maintainer. Using stolen credentials, they published two new releases across both the 1.x and 0.x branches within 39 minutes of each other. The account's registered email was quietly changed to an attacker-controlled ProtonMail address before the releases went out.
Here is what makes this attack stand out: there is zero malicious code inside axios itself. Both releases simply added one new runtime dependency to package.json: a package called plain-crypto-js@4.2.1, which had never appeared in any legitimate axios version.
When you ran npm install, npm resolved and installed that dependency automatically, which triggered its postinstall script and silently executed the dropper.
The malicious dependency: plain-crypto-js
plain-crypto-js@4.2.1 was staged 18 hours before the axios releases, by a separate attacker-controlled account. It masquerades as a clone of the legitimate crypto-js library, with an identical description and the real author's name in the manifest. Visually, it looks harmless. The only difference from the real package is a single extra field in package.json:
"scripts": {
"test": "grunt",
"postinstall": "node setup.js"
}
That setup.js is a cross-platform RAT dropper.
What setup.js does
The dropper detects your operating system and then:
-
macOS: writes an AppleScript to
/tmp, executes it vianohup osascript, downloads a binary to/Library/Caches/com.apple.act.mond(named to blend in with Apple system processes), makes it executable and launches it -
Linux: fetches a Python script from the C2 server to
/tmp/ld.pyand runs it in the background -
Windows: copies PowerShell to
%PROGRAMDATA%\wt.exe(disguised as Windows Terminal), then uses a VBScript to silently fetch and execute a PowerShell payload
All of this runs in under two seconds from the moment npm install starts. The npm install itself exits with code 0 and shows no errors.
The anti-forensics layer
After launching the payload, the dropper:
- Deletes
setup.jsfromnode_modules/plain-crypto-js/ - Deletes the malicious
package.json - Renames a pre-staged clean stub (
package.md) topackage.json, which reports version4.2.0with no scripts
After the swap, running npm audit reveals nothing. Running npm list plain-crypto-js shows version 4.2.0. The only reliable on-disk evidence is the existence of the node_modules/plain-crypto-js/ directory, since this package was never a dependency of any real axios release.
A note on the publish metadata
Every legitimate axios 1.x release is published via GitHub Actions with npm's OIDC Trusted Publisher mechanism, meaning the publish is cryptographically tied to a verified workflow. axios@1.14.1 breaks that pattern entirely. There is no corresponding tag or commit in the axios GitHub repository. It was published manually using a stolen npm access token.
Am I affected?
The malicious versions were live between approximately 00:21 UTC and 03:15 UTC on March 31, 2026. If you did not run npm install (or a fresh npm ci) during that window, you were likely not hit.
Run through these steps to verify.
Step 1: Check for the malicious axios versions
npm list axios 2>/dev/null | grep -E "1\.14\.1|0\.30\.4"
grep -A1 '"axios"' package-lock.json | grep -E "1\.14\.1|0\.30\.4"
No output means you are probably fine. Any output means those versions were installed.
Step 2: Check for plain-crypto-js in node_modules
ls node_modules/plain-crypto-js 2>/dev/null && echo "POTENTIALLY AFFECTED"
If the directory exists, the dropper ran. Do not rely on the version number inside it.
Step 3: Check for RAT artifacts on the 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 (cmd.exe)
dir "%PROGRAMDATA%\wt.exe" 2>nul && echo COMPROMISED
Step 4: Check for C2 activity in logs
grep -r "sfrclak.com" ~/.npm/_logs/ 2>/dev/null
grep "sfrclak" ~/.bash_history ~/.zsh_history 2>/dev/null
Step 5: Check your CI/CD pipelines
Review any npm install runs in GitHub Actions or other CI systems between 00:20 UTC and 03:15 UTC on March 31. Any pipeline that ran with those axios versions should be treated as compromised, and all secrets injected in that workflow should be rotated immediately.
If you got multiple projects running
for dir in ~/projects/*/; do
if [ -f "$dir/package.json" ]; then
result=""
if ls "$dir/node_modules/plain-crypto-js" 2>/dev/null | grep -q .; then
result="DROPPER FOUND"
fi
axios_hit=$(grep -A1 '"axios"' "$dir/package-lock.json" 2>/dev/null | grep -E "1\.14\.1|0\.30\.4")
if [ -n "$axios_hit" ]; then
result="$result | MALICIOUS AXIOS VERSION"
fi
if [ -n "$result" ]; then
echo "⚠️ $dir → $result"
else
echo "✅ $dir"
fi
fi
done
Remediation
1. Downgrade to a safe version
# 1.x users
npm install axios@1.14.0
# 0.x users
npm install axios@0.30.3
2. Pin the version in your package.json
{
"overrides": { "axios": "1.14.0" },
"resolutions": { "axios": "1.14.0" }
}
3. Remove plain-crypto-js and reinstall
rm -rf node_modules/plain-crypto-js
npm install --ignore-scripts
4. Block the C2 domain (macOS / Linux)
echo "0.0.0.0 sfrclak.com" | sudo tee -a /etc/hosts
sudo iptables -A OUTPUT -d 142.11.206.73 -j DROP
5. Add --ignore-scripts to your CI installs going forward
npm ci --ignore-scripts
This prevents any postinstall hook from running during automated builds, which would have stopped this attack entirely.
If you found a RAT artifact
Do not try to clean the machine in place. Rebuild from a known-good state and rotate everything that was accessible during the compromised install: npm tokens, AWS access keys, SSH private keys, cloud credentials, CI/CD secrets, and any .env values present at install time.
Indicators of Compromise
| Category | Value |
|---|---|
| Malicious packages |
axios@1.14.1, axios@0.30.4, plain-crypto-js@4.2.1
|
| C2 domain | sfrclak.com |
| C2 IP | 142.11.206.73 |
| C2 port | 8000 |
| macOS artifact | /Library/Caches/com.apple.act.mond |
| Linux artifact | /tmp/ld.py |
| Windows artifact | %PROGRAMDATA%\wt.exe |
| Safe 1.x version |
axios@1.14.0 (shasum 7c29f4cf2ea91ef05018d5aa5399bf23ed3120eb) |
Takeaways
This attack did not require any vulnerability in axios itself. It exploited the trust developers place in a well-known package name and a familiar maintainer account. The entire weapon was a single extra line in package.json.
A few habits that would have reduced exposure here:
- Run
npm ci --ignore-scriptsin CI pipelines as a standing policy - Use npm's Trusted Publisher provenance statements to verify releases were published from the expected CI workflow
- Watch for new dependencies appearing in transitive installs that were not there before
- Pin your lockfile and review
package-lock.jsondiffs in pull requests
The full technical breakdown, including the decoded dropper, process tree, and file swap events captured at runtime, is in the StepSecurity report.
Stay safe.
Top comments (0)