On March 30, 2026, someone published [email protected] and [email protected] to npm. Both versions pulled in a new dependency called [email protected]. That dependency contained a multi-stage payload that deployed a remote access trojan capable of executing arbitrary commands, exfiltrating system data, and persisting on infected machines.
Axios has 100 million weekly downloads on npm. It sits in the dependency tree of everything from React projects to enterprise backend services. And for roughly 24 hours, anyone who ran npm install with a caret range on Axios got malware.
How It Happened
The attack was surgical. Three steps, each designed to avoid detection.
Step 1: The attacker published [email protected] (a clean typosquat of the legitimate crypto-js library) from the email [email protected]. No malicious code. Just a clean copy establishing the package's existence on npm. That was 18 hours before the main attack.
Step 2: [email protected] went live on March 30 at 23:59:12 UTC, this time with a postinstall hook that triggered the entire attack chain. Socket's automated scanner flagged it within six minutes. But six minutes is an eternity in npm.
Step 3: Within 39 minutes, both [email protected] and [email protected] were published to npm. Neither version appeared in the Axios GitHub repository's tags. Axios typically publishes tagged releases alongside npm versions. This break from the standard workflow was the first red flag. The only meaningful change in these versions was the addition of plain-crypto-js as a dependency.
Any project using ^1.14.0 or ^0.30.0 in their package.json would pull in the compromised version on the next npm install.
The Maintainer Couldn't Stop It
Here's the part that should concern every team that depends on open source.
When the Axios maintainers discovered the compromise, they couldn't immediately fix it. In a public GitHub issue, one collaborator stated they could not revoke access from the account responsible for the malicious publish because the attacker's permissions exceeded their own.
The compromised publisher account was jasonsaayman, associated with the Axios project. Early investigation pointed to continued use of a long-lived npm token alongside trusted publishing, a common pattern that creates exactly this kind of vulnerability.
The maintainers have since focused on revoking tokens, tightening publish controls, and restoring a secure release pipeline. But the window of exposure was real, and projects that pulled in the malicious version during that window need to audit immediately.
What the Malware Actually Did
The malicious setup.js (4,209 bytes) ran automatically via npm's postinstall lifecycle hook. The code used a custom two-layer encoding to evade detection.
Layer 1: Reversed Base64. The encoded string is reversed, underscores replaced with = padding characters, then Base64-decoded.
Layer 2: XOR cipher. Each decoded character is XORed with a digit from the key OrDeR_7077 (selected by index 7*i*i % 10) and the constant 333.
All 18 obfuscated strings, including module names, the C2 URL, shell commands, and file paths, were hidden behind this encoding. This is not minification. It is purpose-built to hide malware indicators from scanners.
The Execution Chain
npm install plain-crypto-js
-> postinstall hook
-> node setup.js
-> detect OS via os.platform()
-> download platform-specific payload from C2
-> delete setup.js
-> rename package.md to package.json (removes all evidence)
After execution, the installed node_modules/plain-crypto-js/ directory looked like a normal copy of crypto-js. No trace of malicious code remained on disk.
Platform-Specific Payloads
The attack was tailored to every major operating system.
macOS: An AppleScript dropper downloaded a binary to /Library/Caches/com.apple.act.mond, deliberately mimicking Apple's com.apple.* naming convention to look like a system daemon. The binary was code-signed ad-hoc and launched via /bin/zsh in the background.
Windows: The attack located PowerShell via where powershell and copied it to %PROGRAMDATA%\wt.exe, disguising it as Windows Terminal to evade EDR detection. A hidden VBScript wrapper launched curl to download a .ps1 script to %TEMP%\6202033.ps1 and executed it with -w hidden -ep bypass flags. Both the VBS and PS1 files self-deleted after execution.
Linux: The simplest variant. A Python script downloaded to /tmp/ld.py via curl and launched detached with nohup python3.
Each platform sent a distinct POST body to the C2 server at sfrclak[.]com:8000, using packages[.]npm[.]org/product0 (macOS), /product1 (Windows), and /product2 (Linux) as campaign identifiers. The domain npm.org is not the npm registry (that's registry.npmjs.org). It belongs to the National Association of Pastoral Musicians and has since 1997. The naming was deliberate: network monitoring tools would log what looked like routine npm traffic at first glance.
The macOS RAT
Security researcher Joe Desimone from Elastic Security captured the macOS second-stage binary before the C2 went offline. It was a fully functional remote access trojan written in C++.
On launch, the RAT generated a 16-character unique victim ID and fingerprinted the system: hostname, username, macOS version, timezone, CPU type, OS install date, boot time, running processes, and directory listings of /Applications, ~/Library, and ~/Application Support. It beaconed to the C2 via HTTP POST every 60 seconds with all data Base64-encoded.
The RAT supported four commands:
- peinject: Receives a Base64-encoded binary, writes it to a hidden temp file, ad-hoc code signs it, and executes it. Full arbitrary payload deployment.
- runscript: Executes shell commands via
/bin/shor encoded AppleScripts via/usr/bin/osascript. - rundir: Enumerates directories with name, size, timestamps, and hierarchy.
- kill: Terminates the RAT.
This was not a proof of concept. It was a production-grade backdoor.
The Cascade Effect
Socket identified two additional packages distributing the same malware through vendored dependencies. @shadanai/openclaw (versions 2026.3.31-1 and 2026.3.31-2) contained the identical setup.js file buried deep in a vendored path. @qqbrowser/[email protected] shipped a tampered [email protected] in its own node_modules/ with plain-crypto-js injected as a dependency.
This is the cascading reality of supply chain attacks. One compromised package becomes two, then four, then dozens. Automated build pipelines and AI-assisted package publishing accelerate the spread. By the time humans notice, the malware has already propagated through the dependency graph.
Why This Keeps Happening
Axios gets 100 million downloads a week. It has four npm maintainers. The library's publishing workflow relied partly on a long-lived npm token that should have been rotated or replaced with more restrictive publishing controls years ago.
This is the structural problem at the heart of open source security: the packages the entire industry depends on are often maintained by a small number of people with limited security resources. npm tokens don't expire by default. Two-factor authentication on npm accounts is optional. And the postinstall hook, which gives arbitrary code execution during npm install, is still enabled by default.
The Axios attack follows a pattern we've seen repeatedly:
- event-stream (2018): A maintainer handed off control to a stranger who injected a cryptocurrency wallet stealer. 8 million downloads per week.
- ua-parser-js (2021): Hijacked npm account published crypto miners. 40 million downloads per week.
- colors and faker (2022): Maintainer deliberately sabotaged his own packages. Combined 23 million weekly downloads.
- Axios (2026): Account compromise, trojanized dependency. 100 million downloads per week.
The blast radius keeps getting bigger. The techniques keep getting more sophisticated. The attacker's anti-forensics (self-deleting scripts, clean package restoration) represent a meaningful evolution in supply chain malware.
What You Should Do Right Now
1. Check Your Lockfiles
Search your package-lock.json, yarn.lock, or pnpm-lock.yaml for:
[email protected][email protected]plain-crypto-js(any version)
If any of these appear, assume the machine that ran npm install was compromised.
2. Pin Your Dependencies
Stop using caret ranges (^1.14.0) for critical dependencies. Use exact versions or ranges you've explicitly audited. Yes, this means more manual work when upgrading. That's the point. Automatic upgrades to unaudited versions are exactly how supply chain attacks succeed.
3. Disable postinstall Hooks
npm's --ignore-scripts flag prevents lifecycle scripts from running during install. This breaks some legitimate packages that need postinstall compilation, but it eliminates the most common malware execution vector in npm.
npm install --ignore-scripts
For packages that genuinely need postinstall, whitelist them explicitly using .npmrc:
ignore-scripts=true
# Allow specific packages
script-shell=/bin/bash
4. Audit Your npm Publishing Workflow
If you publish packages to npm:
- Rotate your npm tokens regularly
- Use npm's granular access tokens with publish-only scope
- Enable mandatory 2FA on your npm account
- Use npm's provenance feature (links published packages to CI builds)
- Consider using trusted publishing (OIDC-based, no stored tokens)
5. Monitor Your Dependencies
Use tools that actually scan for malware indicators, not just known CVEs. Socket, Snyk, and npm audit catch different things. No single tool catches everything.
The Bigger Question Nobody Wants to Answer
The JavaScript ecosystem's dependency model treats npm install as a safe operation. It is not. Every npm install is an implicit trust decision: you trust every maintainer of every package in your dependency tree, and every maintainer of their dependencies, and so on.
With Axios, that trust chain included a single account with a long-lived token and no publish-time verification. One compromised credential gave an attacker the ability to push malware to 100 million weekly installs.
The tooling exists to mitigate this. Lockfile pinning, script disabling, provenance verification, automated dependency scanning. Most teams don't use all of them. Many don't use any.
We've built our development workflow around the assumption that any dependency could be compromised at any time. Every project we ship uses pinned dependencies, disabled lifecycle scripts by default, automated dependency scanning in CI, and regular audit cycles. It adds friction to the development process. That friction is the point.
If you're running a production application that depends on npm packages (which is nearly every JavaScript project), your supply chain security posture determines your actual security posture. The Axios attack proved that the most popular, most trusted packages are the most attractive targets.
Review your dependency security with us. We'll audit your current setup, identify the highest-risk packages in your dependency tree, and build automated guardrails that catch supply chain attacks before they reach production.