Summary
Two malicious versions of axios — the most popular JavaScript HTTP client library with over 100 million weekly downloads — were published to npm: axios@1.14.1 and axios@0.30.4. A fake dependency, plain-crypto-js@4.2.1, was injected as a runtime dependency. This package contained a post-install script that deployed a remote access trojan (RAT) within two seconds of npm install, before npm had even finished resolving the rest of the dependency tree. The malware then phoned home to the attacker's command-and-control server. Every artifact was designed to self-destruct after execution, leaving minimal forensic traces.
How the Attack Was Carried Out
Stage 1 — Maintainer Account Hijacked
The axios maintainer's npm account was hijacked. The infected packages were pushed directly from this account to the npm registry, completely bypassing GitHub's normal CI/CD pipeline. Notably, version 1.14.1 does not exist on GitHub at all — it was published exclusively through the compromised npm account, making it invisible to anyone monitoring the GitHub repository for changes.
Stage 2 — Staging the Fake Dependency (18 Hours Before)
Before publishing the malicious axios versions, the attacker staged a fake package: plain-crypto-js@4.2.1. This package was published from an anonymous Proton Mail account.
The name was deliberately chosen to appear legitimate — HTTP libraries routinely depend on cryptographic packages for TLS, request signing, and response decoding. The fake package reused the same description and website reference as the legitimate crypto-js package, making it difficult to distinguish at a glance.
The timing was intentional: plain-crypto-js was published 18 hours before the infected axios versions. Publishing both simultaneously would have raised suspicion. The delay made the dependency appear to have an independent existence.
The fake dependency contained:
- A
postinstallscript that fires the RAT immediately upon installation - A mechanism that generates a clean
package.jsonas a markdown artifact, then replaces the infectedpackage.jsonwith the clean version after execution — erasing its own footprints
Stage 3 — Injecting the Dependency into Axios
Within 39 minutes of each other, two axios versions were published:
-
axios@1.14.1(targeting users on the latest 1.x branch) -
axios@0.30.4(targeting users still on the legacy 0.x branch)
Both versions were identical to their legitimate predecessors, with a single addition: plain-crypto-js: "^4.2.1" as a runtime dependency. The package is never imported or called anywhere in axios source code — it exists solely as a vehicle to trigger the postinstall script and deploy the RAT.
Stage 4 — Payload Execution
The postinstall script in plain-crypto-js executes immediately during npm install. Three OS-specific payloads were prepared, targeting:
- Linux
- macOS
- Windows
Within two seconds of installation, the malware was already calling home to the attacker's command-and-control server at http://sfrclak.com:8000/ — before npm had even finished resolving the remaining dependencies. After execution, the payload self-destructs, replacing the infected package.json with a clean version to cover its tracks.
Why This Attack Was Hard to Catch
-
No code changes in axios itself: The axios source code was untouched. Only
package.jsonwas modified to add a single dependency. -
The dependency looked plausible:
plain-crypto-jshad a convincing name, matching description, and was published well in advance. - Bypassed CI/CD: The versions were published directly to npm, never appearing in the GitHub repository.
- Self-destructing artifacts: The malware cleaned up after itself, making post-mortem analysis more difficult.
- Two branches poisoned simultaneously: Both the modern (1.x) and legacy (0.x) branches were targeted, maximizing coverage.
How to Check If You're Compromised
Check Your Installed Version
npm ls axios
If you see axios@1.14.1 or axios@0.30.4, you are affected.
Check for the Fake Dependency
npm ls plain-crypto-js
If this returns any result, the malicious dependency is present in your node_modules.
Check Network Logs
Look for outbound connections to sfrclak.com:8000 from any system that ran npm install during the compromise window.
Check for Residual Artifacts
Although the malware self-destructs, incomplete cleanup may leave traces. Search for references to plain-crypto-js in lock files:
grep -r "plain-crypto-js" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
Remediation
-
Uninstall the compromised versions:
npm uninstall axios npm install axios@1.14.0 # or latest known safe version -
Remove the fake dependency:
npm uninstall plain-crypto-js -
Rotate all secrets on affected machines: Any credential, token, or key accessible from the environment where the RAT executed should be considered compromised and rotated immediately.
-
Audit systems for persistent access: The RAT may have established persistence beyond the initial payload. Check for:
- Unexpected cron jobs or scheduled tasks
- Unknown processes or services
- Unauthorized SSH keys
-
Update lock files: Regenerate
package-lock.json/yarn.lock/pnpm-lock.yamlfrom a clean state to ensure no references toplain-crypto-jsremain.
Defensive Takeaways
- Use lock files and review dependency changes: A new runtime dependency appearing in a patch version is a red flag. Lock files make this visible during code review.
-
Disable postinstall scripts: Consider using
--ignore-scriptsduring installation, or use tools likeallow-scriptsto whitelist which packages can run lifecycle scripts. - Monitor npm for direct publishes: If a package version exists on npm but not in the corresponding GitHub repository, treat it as suspicious.
- Enable 2FA on package registries: Maintainer account hijacking remains the most common entry point. Mandatory 2FA on npm, PyPI, and other registries is critical.
-
Pin exact dependency versions: Use exact versions rather than ranges (
^,~) to prevent automatic resolution to a compromised version.