100M+
Weekly Downloads
80%
Cloud Environments
39min
Both Branches Hit
36sec
Install to Cleanup
The Story
The attack was methodical. On March 30 at 05:57 UTC, a clean version of plain-crypto-js@4.2.0 was published to npm by user "nrwise" (nrwise@proton.me). This was a faithful copy of the legitimate crypto-js library with the same description, author name, and repository URL. It contained no malicious code. It existed to establish a credible package identity on npm.
Eighteen hours later, at 23:59 UTC on March 30, plain-crypto-js@4.2.1 was published. This version contained the malicious postinstall script. The dependency was now staged and ready.
At 00:21 UTC on March 31, the attacker used the compromised "jasonsaayman" npm account to publish axios@1.14.1. At 01:00 UTC, axios@0.30.4 followed. Both the 1.x and 0.x release branches were poisoned within 39 minutes. Neither version went through Axios's standard GitHub Actions CI/CD pipeline. They were published directly via the npm CLI using the stolen account credentials. No corresponding GitHub tags exist for these versions.
The malicious versions of Axios contain no changes to the Axios source code itself. The only modification is the addition of plain-crypto-js@4.2.1 as a dependency in package.json. This dependency is never imported by Axios. It exists solely to execute its postinstall hook during npm install.
Wiz reported that Axios is present in approximately 80% of cloud and code environments they monitor, and confirmed execution of the malicious versions in 3% of affected environments during the brief window before removal. Socket's automated malware detection flagged the malicious plain-crypto-js package within 6 minutes of publication. The malicious versions were removed from npm within hours, but the damage window was real.
Help Net Security noted that the absence of cryptocurrency mining or ransomware components, combined with the operational sophistication of the attack, "indicates that this is not financially motivated cybercrime, but rather espionage or advanced persistent threat (APT) activity." The attack is not currently attributed to TeamPCP or any known threat actor.
The Adversary Perspective
This is one of the most technically sophisticated npm supply chain attacks ever documented. Four design choices reveal a highly capable operator.
First, the dependency injection model avoids touching Axios source code. The attacker did not modify any Axios JavaScript files. They added a single line to package.json introducing a new dependency. This means code review tools, diff analysis, and source integrity checks that compare .js files against the GitHub repository would not detect the compromise. The malicious behavior lives entirely in the dependency's postinstall hook, one level removed from the package the developer trusts.
Second, the anti-forensic self-deletion is unusually thorough. After execution, the dropper deletes setup.js, removes the package.json that contained the postinstall hook, and replaces it with a clean version. Anyone inspecting node_modules/plain-crypto-js after the fact sees a completely innocent-looking package. The only forensic indicator is the presence of the plain-crypto-js directory itself in node_modules, which most developers would not recognize as anomalous. The entire install-to-cleanup cycle completes in 36 seconds.
Third, the RAT is purpose-built for developer machine compromise. Each platform variant is tailored. The macOS RAT disguises itself as com.apple.act.mond, spoofing Apple's background daemon naming convention. The Windows variant copies PowerShell to a non-standard location and executes hidden scripts. The Linux variant uses a Python script. All three beacon to C2 every 60 seconds using a fake IE8/Windows XP User-Agent string to blend with common web traffic patterns. The RAT supports commands for system fingerprinting, credential harvesting, process injection, and arbitrary code execution.
Fourth, the staging and timing demonstrate deliberate operational planning. The clean dependency was published 18 hours before the malicious version. The account email was changed to lock out the legitimate maintainer. Both release branches were hit in 39 minutes to maximize the window before detection. The attack was designed to execute, persist, and clean up before the ecosystem's automated detection systems could intervene at scale. Socket caught it in 6 minutes, but the attacker anticipated this. The 36-second cleanup cycle means that by the time a detection alert fires, the evidence is already gone from the package directory.
Figure 01
- MAR 30 05:57 UTC
Clean Dependency Staged
plain-crypto-js@4.2.0 published to npm. Faithful copy of legitimate crypto-js. No malicious code. Establishes credible package identity. Published by "nrwise" (nrwise@proton.me). - MAR 30 23:59 UTC
Malicious Version Published
plain-crypto-js@4.2.1 published with malicious postinstall script (setup.js). Obfuscated Node.js dropper with XOR encoding and base64 transformations. Cross-platform RAT delivery for macOS, Windows, Linux. - MAR 31 00:21 UTC // CRITICAL
axios@1.14.1 Published
Compromised "jasonsaayman" npm account used to publish poisoned 1.x branch. Account email changed to Proton Mail. Published via npm CLI, bypassing GitHub Actions CI/CD. Injects plain-crypto-js@4.2.1 as dependency. - MAR 31 01:00 UTC // CRITICAL
axios@0.30.4 Published
0.x branch poisoned 39 minutes after 1.x. Any project using ^1.14.0 or ^0.30.0 caret ranges pulls compromised version on next npm install. Both major release lines now deliver the RAT. - INSTALL TIME // CRITICAL
RAT Dropped in 36 Seconds
npm install triggers plain-crypto-js postinstall hook. Dropper contacts C2 at sfrclak.com:8000. Downloads OS-specific RAT payload. Establishes persistence. Deletes setup.js, removes malicious package.json, replaces with clean decoy. Full cleanup in 36 seconds. - PERSISTENT
RAT Beacons Every 60 Seconds
macOS: /Library/Caches/com.apple.act.mond (spoofed Apple daemon). Windows: %PROGRAMDATA%\wt.exe (renamed PowerShell). Linux: /tmp/ld.py (background Python script). All beacon to C2 with system fingerprint, awaiting commands for credential harvest, process injection, or arbitrary execution.
IOC & Detection Summary
- Compromised Versions: axios@1.14.1, axios@0.30.4
- Safe Versions: axios@1.14.0, axios@0.30.3
- Malicious Dependency: plain-crypto-js@4.2.1 (npm user "nrwise", nrwise@proton.me)
- Compromised Account: jasonsaayman (email changed to ifstap@proton.me)
- C2 Server: sfrclak.com:8000 (IP: 142.11.206.73)
- C2 Beacon Interval: 60 seconds
- macOS RAT Path: /Library/Caches/com.apple.act.mond
- Windows RAT Path: %PROGRAMDATA%\wt.exe
- Linux RAT Path: /tmp/ld.py
- Cleanup Window: 36 seconds (install to evidence deletion)
- Detection: Presence of plain-crypto-js directory in node_modules
- npm Status: Malicious versions removed, plain-crypto-js quarantined
- Wiz Observation: Present in 80% of cloud environments, executed in 3%
- Attribution: Unknown. APT-level sophistication. Not linked to TeamPCP.
Defensive Takeaways
- 1
Check Every Environment for axios 1.14.1 or 0.30.4 Right Now
Run
npm ls axiosin every project, CI/CD runner, Docker image, and developer machine. If either compromised version was installed at any point, treat that system as fully compromised. Do not attempt to clean in place. Rebuild from a known-good state. Rotate all credentials that were accessible from the compromised environment: npm tokens, AWS keys, SSH private keys, cloud credentials (GCP, Azure), CI/CD secrets, and anything in .env files. The RAT self-deletes its dropper but the persistent binary remains on the system. - 2
Check for RAT Artifacts on Developer Machines
Search for the platform-specific RAT locations:
/Library/Caches/com.apple.act.mondon macOS,%PROGRAMDATA%\wt.exeon Windows,/tmp/ld.pyon Linux. If found, the system is confirmed compromised. Also monitor for outbound connections tosfrclak.com:8000(142.11.206.73). The RAT beacons every 60 seconds, so network monitoring over even a short window should detect it. Block the C2 domain and IP at your firewall immediately. - 3
Disable postinstall Scripts in CI/CD Pipelines
The entire attack chain depends on npm executing the postinstall hook during
npm install. Use the--ignore-scriptsflag in CI/CD pipelines to prevent postinstall hooks from running during automated builds. This is the single most effective mitigation against this entire class of supply chain attack. If a dependency legitimately requires a postinstall script, review and approve it explicitly rather than allowing arbitrary execution by default. - 4
Pin Dependencies to Exact Versions, Not Caret Ranges
Any project using
^1.14.0or^0.30.0as its Axios version range would have automatically pulled the compromised version on the nextnpm install. Caret ranges are convenient but they automatically trust every new minor and patch release a package author (or attacker) publishes. Pin critical dependencies to exact versions in production. Use a lockfile (package-lock.json) and commit it to your repository. Review lockfile changes in pull requests before merging. - 5
Require MFA and Short-Lived Tokens for npm Publishing
The attacker used a long-lived classic npm access token to take over the maintainer account and publish directly. npm supports granular access tokens with IP restrictions, CIDR ranges, and expiration dates. It also supports mandatory 2FA for publishing. If you maintain npm packages, enable mandatory 2FA for all publishing operations, use automation tokens scoped to specific packages and CIDR ranges, and set token expiration to the shortest practical lifetime. If you consume npm packages, advocate for these practices in the open source projects you depend on. The security of your supply chain depends on the security practices of your dependencies' maintainers.