Security: Axios Supply-Chain Attack Scanner
Mission
Scan the entire Mac for the axios npm supply-chain compromise (2026-03-31).
Check package.json and all lock files (package-lock.json, yarn.lock, pnpm-lock.yaml).
Detect anti-forensics (dropper self-cleanup), related campaign packages, and verify RAT SHA256.
Produce a full HTML threat report and open it automatically.
Background
On 2026-03-31, axios versions 1.14.1 and 0.30.4 were published via a compromised maintainer
account (jasonsaayman, email changed to ifstap@proton.me) with an injected dependency
plain-crypto-js@4.2.1. Its postinstall hook (setup.js) deployed a cross-platform RAT that
beacons to sfrclak[.]com:8000 every 60 seconds. On macOS the RAT binary is
/Library/Caches/com.apple.act.mond. The dropper self-destructs after execution: setup.js is
deleted and package.json is replaced with a clean stub reporting version 4.2.0 — so
post-infection node_modules inspection will NOT reveal the malicious manifest. The directory
presence of node_modules/plain-crypto-js/ alone confirms the dropper ran.
The malicious dependency was pre-staged ~18 hours earlier by attacker account nrwise
(nrwise@proton.me). A clean decoy version (4.2.0) was published first to build registry history.
Both compromised axios versions were published manually with a stolen npm token — no OIDC Trusted
Publisher binding, no gitHead, no corresponding GitHub commit or tag.
References
| Source | URL |
|---|---|
| Snyk Advisory | https://snyk.io/blog/axios-npm-package-compromised-supply-chain-attack-delivers-cross-platform/ |
| Wiz Blog | https://www.wiz.io/blog/axios-npm-compromised-in-supply-chain-attack |
| StepSecurity Blog | https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan |
| Aikido Blog | https://www.aikido.dev/blog/axios-npm-compromised-maintainer-hijacked-rat |
| GitHub Issue #10604 | https://github.com/axios/axios/issues/10604 |
| GitHub Advisory | https://github.com/advisories/GHSA-fw8c-xr5c-95f9 |
| OSV Advisory | https://osv.dev/vulnerability/MAL-2026-2306 |
| Elastic Security Gist | https://gist.github.com/joe-desimone/36061dabd2bc2513705e0d083a9673e7 |
Threat Intelligence
| Category | Value |
|---|---|
| Compromised axios versions | 1.14.1, 0.30.4 |
| Malicious dependency | plain-crypto-js@4.2.1 |
| Related campaign packages | @shadanai/openclaw (v2026.3.28-2, 2026.3.28-3, 2026.3.31-1, 2026.3.31-2), @qqbrowser/openclaw-qbot (v0.0.130) |
| macOS RAT binary | /Library/Caches/com.apple.act.mond |
| macOS RAT SHA256 | 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a |
| Windows RAT | %PROGRAMDATA%\wt.exe (PowerShell copy), %TEMP%\6202033.ps1, %TEMP%\6202033.vbs |
| Windows RAT SHA256 | 617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 (PS1) |
| Linux RAT | /tmp/ld.py |
| Linux RAT SHA256 | fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf |
| C2 domain | sfrclak[.]com |
| C2 IP | 142.11.206.73 |
| C2 port | 8000 |
| Campaign path | /6202033 |
| C2 POST body (macOS) | packages.npm.org/product0 |
| C2 POST body (Windows) | packages.npm.org/product1 |
| C2 POST body (Linux) | packages.npm.org/product2 |
| Infection vector | npm install / yarn install / pnpm install postinstall hook |
| Compromised account | jasonsaayman (email changed to ifstap@proton.me) |
| Attacker account | nrwise (nrwise@proton.me) — published plain-crypto-js |
| GitHub Advisory | GHSA-fw8c-xr5c-95f9 |
| OSV Advisory | MAL-2026-2306 |
| Attack window | 2026-03-31 00:21–03:15 UTC (~3 hours) |
| Windows persistence | Registry Run key MicrosoftUpdate, re-download batch file |
| macOS dropper method | AppleScript via osascript writing to /tmp/6202033 |
Package SHA256 Hashes
| Package | SHA256 |
|---|---|
axios-1.14.1.tgz |
5bb67e88846096f1f8d42a0f0350c9c46260591567612ff9af46f98d1b7571cd |
axios-0.30.4.tgz |
59336a964f110c25c112bcc5adca7090296b54ab33fa95c0744b94f8a0d80c0f |
plain-crypto-js-4.2.1.tgz |
58401c195fe0a6204b42f5f90995ece5fab74ce7c69c67a24c61a057325af668 |
com.apple.act.mond (macOS) |
92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a |
stage2.ps1 (Windows) |
617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 |
ld.py (Linux) |
fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf |
Package SHA1 Shasums (npm)
| Package | SHA1 |
|---|---|
axios@1.14.1 |
2553649f232204966871cea80a5d0d6adc700ca |
axios@0.30.4 |
d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71 |
plain-crypto-js@4.2.1 |
07d889e2dadce6f3910dcbc253317d28ca61c766 |
Anti-Forensics Awareness
The dropper (setup.js) performs three cleanup actions after execution:
- Deletes
setup.js—fs.unlink(__filename)removes itself - Deletes
package.json— removes the manifest with"postinstall": "node setup.js" - Renames
package.mdtopackage.json— a pre-staged clean stub reporting version4.2.0
Post-infection, npm list shows plain-crypto-js@4.2.0 (not 4.2.1). Incident responders
checking for version 4.2.1 may incorrectly conclude the system is clean. The reliable check is
directory presence: if node_modules/plain-crypto-js/ exists at all, the dropper ran — this
package is not a dependency of ANY legitimate axios version.
Our scanner checks:
node_modules/plain-crypto-js/directory existence (confirms dropper ran)node_modules/plain-crypto-js/setup.jspresence (dropper not yet cleaned up)
Execution Flow
GREP PRE-FILTER → PARSE → LOCK FILES → RELATED PKGS → IOC CHECK → SHA256 VERIFY → LAUNCH PERSIST → REPORT → OPEN
What Is Scanned
| File / Check | What We Check |
|---|---|
package.json |
declared axios version, plain-crypto-js in all dep sections, related campaign packages |
package-lock.json |
resolved axios version, plain-crypto-js in packages tree, related campaign packages |
yarn.lock |
resolved axios version, any mention of plain-crypto-js |
pnpm-lock.yaml |
resolved axios version, any mention of plain-crypto-js |
node_modules/plain-crypto-js/ |
directory presence (confirms dropper ran even after self-cleanup) |
node_modules/plain-crypto-js/setup.js |
dropper file still present (not yet cleaned up) |
/Library/Caches/com.apple.act.mond |
RAT binary present + SHA256 hash verification against known IOC |
running processes via pgrep |
RAT process (FP-safe — pgrep excludes itself) |
lsof -i |
active C2 connection to sfrclak.com or 142.11.206.73 |
~/Library/LaunchAgents/, /Library/LaunchAgents/, /Library/LaunchDaemons/ |
launch persistence referencing act.mond, sfrclak, or C2 IP |
| global npm/yarn/pnpm | plain-crypto-js installed globally |
False-Positive Notes
Process check FP risk: NEVER use ps aux | grep act.mond — a bash heredoc containing that string
would match itself. Use pgrep -f "/Library/Caches/com.apple.act.mond" instead — pgrep excludes
its own PID, and the script runs as python3 /tmp/file.py (no script content in command line).
Anti-forensics version mismatch: After dropper cleanup, npm list plain-crypto-js reports
4.2.0 (not 4.2.1). The scanner checks directory existence rather than version number.
Scripts
The scanner is split into focused modules under scripts/:
skills/security-axios/scripts/
├── axios_scan.py # Entry point — orchestrates scan, IOC check, report
├── constants.py # Threat intel: IOC hashes, versions, references, skip dirs
├── scanners.py # Manifest/lock file parsers + find_and_scan walker
├── ioc_check.py # System IOC checks: RAT, C2, globals, launch persistence
└── report.py # HTML report generation: CSS, tables, badges, template
To run:
python3 /Users/guybary/Documents/skills/skills/security-axios/scripts/axios_scan.py
Module Reference
constants.py — Threat Intelligence Data
All IOC constants, SHA256 hashes, related campaign packages, skip directories, and reference URLs.
scanners.py — Manifest & Lock File Parsers
| Function | Purpose |
|---|---|
scan_package_json(path) |
Scan for axios, plain-crypto-js, and related campaign packages in all dep sections |
scan_package_lock(path) |
Scan npm lockfile v1/v2/v7+ packages tree, including related packages |
scan_yarn_lock(path) |
Regex-based scan for compromised resolved versions |
scan_pnpm_lock(path) |
Regex-based scan for pnpm lockfile v5/v6/v9 formats |
find_and_scan() |
Walk ~/, merge findings per repo, track dropper evidence (plain_installed, setup_js_present) |
quick_hit(path, *needles) |
Bytes-level grep pre-filter before full parse |
check_related_pkgs(all_deps) |
Check dependency map for @shadanai/openclaw, @qqbrowser/openclaw-qbot |
ioc_check.py — System IOC Detection
| Function | Purpose |
|---|---|
check_iocs() |
Returns (rat_found, rat_hash_match, rat_running, c2_active, global_crypto, launch_persist) |
sha256_file(path) |
Compute SHA256 for IOC hash verification |
Checks performed:
- RAT binary existence + SHA256 verification against known hash
pgrep -ffor running RAT process (FP-safe)lsof -ifor active C2 connections- npm/yarn/pnpm global list for
plain-crypto-js - LaunchAgents/LaunchDaemons scan for persistence references
report.py — HTML Report Generation
Generates dark-themed HTML report with:
- Threat intel section (SHA256 hashes, campaign path, advisory IDs)
- Project tables with dropper evidence and related packages columns
- Source badges (📦 package.json · 🔒 lockfile · 🧶 yarn · ⚡ pnpm)
- References & Advisories section with all 8 source links
- Remediation steps including version pinning and CI/CD
--ignore-scripts - Footer with advisory links (GHSA, OSV, Snyk, Wiz, StepSecurity, Aikido)
Execution Instructions
When this skill is invoked:
Announce — scanning for the axios supply-chain attack (2026-03-31).
Run the scanner script directly — do NOT copy/paste inline:
python3 /Users/guybary/Documents/skills/skills/security-axios/scripts/axios_scan.py
What the script does:
- Walk
~/skippingnode_modules,Library, caches, etc. - Use bytes-level grep pre-filter before full JSON/YAML parsing (fast)
- Scan
package.json,package-lock.json,yarn.lock,pnpm-lock.yamlper repo - Check for related campaign packages (
@shadanai/openclaw,@qqbrowser/openclaw-qbot) - Detect dropper anti-forensics (directory presence vs
setup.jspresence) - Check macOS IOC with
pgrep -f RAT_PATH(NOTps aux | grep— avoids FP) - SHA256 verify RAT binary against known hash
92ff0877...c9645a - Check C2 connections via
lsof -i - Check global npm/yarn/pnpm for
plain-crypto-js - Scan LaunchAgents/LaunchDaemons for persistence referencing attack IOCs
- Generate dark-themed HTML with source badges (📦 🔒 🧶 ⚡) and all references
- Open in browser automatically
- Walk
Print the terminal summary.
Verdict:
🚨 SYSTEM COMPROMISED— RAT binary / C2 active / launch persistence⚠ VULNERABLE DEPS FOUND— compromised axios in lock files / package.json✅ CLEAN— no compromised versions, no IOCs
List each compromised repo with path, version, which file(s) confirmed it, and related packages.
Constraints
- NEVER output credential values from any scanned file.
- NEVER modify any files. Read-only scan.
- Skip
node_modules/when walking for manifests. - FP rule: use
pgrep -f RAT_PATH— neverps aux | grep act.mond.
Success Criteria
- All
package.json+ lock files scanned (outsidenode_modules) - Compromised axios
1.14.1/0.30.4detected in package.json AND lock files -
plain-crypto-js@4.2.1detected in all manifest types - Related campaign packages (
@shadanai/openclaw,@qqbrowser/openclaw-qbot) detected - yarn.lock and pnpm-lock.yaml scanned for resolved versions
- Anti-forensics detected (plain-crypto-js directory presence + setup.js check)
- macOS RAT IOC checked (binary + SHA256 verification + pgrep process + lsof C2)
- Launch persistence checked (LaunchAgents/LaunchDaemons)
- Global npm/yarn/pnpm checked for plain-crypto-js
- HTML report with source badges (📦 package.json · 🔒 lockfile · 🧶 yarn · ⚡ pnpm)
- HTML report includes all 8 references (Snyk, Wiz, StepSecurity, Aikido, GitHub Issue, GHSA, OSV, Elastic)
- HTML report includes SHA256 hashes and advisory IDs in threat intel section
- Report auto-opened in browser
- No false positives from process detection