Report ID: TI-AXIOS-20260331 | CVEs: GHSA-fw8c-xr5c-95f9 · MAL-2026-2306
Affected: axios@1.14.1 · axios@0.30.4 · plain-crypto-js@4.2.1
Exposure window: 2026-03-31 00:21 UTC → 03:15 UTC (~3 hours)
Classification: TLP:AMBER — Limited Distribution
The primary maintainer account for axios (300M+ weekly downloads) was compromised via a leaked long-lived npm access token. Attackers published two poisoned versions that silently installed a cross-platform Remote Access Trojan (RAT) via the postinstall hook of an injected transitive dependency (plain-crypto-js@4.2.1). Payloads were delivered for macOS, Windows, and Linux. The RAT self-deletes after execution — absence of artifacts does not mean safety.
| Indicator | Type | Severity |
|---|---|---|
sfrclak.com |
C2 Domain | 🔴 CRITICAL |
142.11.206.73 |
C2 IP | 🔴 CRITICAL |
sfrclak.com:8000 |
C2 Host:Port | 🔴 CRITICAL |
| Indicator | Platform | Severity |
|---|---|---|
/Library/Caches/com.apple.act.mond |
macOS | 🔴 CRITICAL |
/tmp/.*.scpt |
macOS | 🔴 CRITICAL |
%PROGRAMDATA%\wt.exe |
Windows | 🔴 CRITICAL |
/tmp/ld.py |
Linux | 🔴 CRITICAL |
| Indicator | Severity |
|---|---|
plain-crypto-js@4.2.1 |
🔴 CRITICAL |
axios@1.14.1 |
🔴 CRITICAL |
axios@0.30.4 |
🔴 CRITICAL |
@shadanai/openclaw |
🔴 CRITICAL |
@qqbrowser/openclaw-qbot@0.0.130 |
🔴 CRITICAL |
| Indicator | Use |
|---|---|
OrDeR_7077 |
XOR key — use in YARA signatures |
ifstap@proton.me |
Attacker email (compromised maintainer account) |
nrwise@proton.me |
Attacker email (published plain-crypto-js) |
Run this first. Exits
0(clean) or1(IOC found). Safe to run in CI.
#!/usr/bin/env bash
# detect-axios-ioc.sh — TI-AXIOS-20260331
# Usage: chmod +x detect-axios-ioc.sh && ./detect-axios-ioc.sh
set -euo pipefail
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
COMPROMISED=0; WARNINGS=0; FINDINGS=()
log_ok() { echo -e " ${GREEN}✓${RESET} $1"; }
log_warn() { echo -e " ${YELLOW}⚠${RESET} $1"; WARNINGS=$((WARNINGS+1)); FINDINGS+=("WARN: $1"); }
log_crit() { echo -e " ${RED}✗ [CRITICAL]${RESET} $1"; COMPROMISED=1; FINDINGS+=("CRITICAL: $1"); }
section() { echo -e "\n${CYAN}${BOLD}━━━ $1 ━━━${RESET}"; }
echo -e "${RED}${BOLD}"
echo " ╔══════════════════════════════════════════════════════════════╗"
echo " ║ AXIOS SUPPLY CHAIN ATTACK — IOC DETECTION ║"
echo " ║ TI-AXIOS-20260331 | GHSA-fw8c-xr5c-95f9 | MAL-2026-2306 ║"
echo " ╚══════════════════════════════════════════════════════════════╝"
echo -e "${RESET}"
echo " Platform : $(uname -s) $(uname -m)"
echo " Hostname : $(hostname)"
echo " Date : $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo " Run as : $(whoami)"
# ── 1. Lockfiles ──────────────────────────────────────────────────────────────
section "1/5 LOCKFILE & DEPENDENCY SCAN"
mapfile -t LOCKFILES < <(find . -maxdepth 6 \
\( -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" \) \
-not -path "*/node_modules/*" 2>/dev/null || true)
if [[ ${#LOCKFILES[@]} -eq 0 ]]; then
log_warn "No lockfiles found (searched 6 levels deep)"
else
for lf in "${LOCKFILES[@]}"; do
if grep -q 'plain-crypto-js' "$lf" 2>/dev/null; then
grep -q '4\.2\.1' "$lf" 2>/dev/null \
&& log_crit "plain-crypto-js@4.2.1 in $lf — MALICIOUS DROPPER" \
|| log_warn "plain-crypto-js (non-4.2.1) in $lf — verify manually"
else
log_ok "plain-crypto-js not in $lf"
fi
grep -qE '"axios".*"1\.14\.1"|axios@1\.14\.1' "$lf" 2>/dev/null \
&& log_crit "axios@1.14.1 in $lf — COMPROMISED VERSION"
grep -qE '"axios".*"0\.30\.4"|axios@0\.30\.4' "$lf" 2>/dev/null \
&& log_crit "axios@0.30.4 in $lf — COMPROMISED VERSION"
grep -q '@shadanai/openclaw' "$lf" 2>/dev/null && log_crit "@shadanai/openclaw in $lf"
grep -q '@qqbrowser/openclaw-qbot' "$lf" 2>/dev/null && log_crit "@qqbrowser/openclaw-qbot in $lf"
done
fi
# Installed node_modules
if [[ -d "./node_modules/plain-crypto-js" ]]; then
VER=$(node -e "try{console.log(require('./node_modules/plain-crypto-js/package.json').version)}catch(e){console.log('unknown')}" 2>/dev/null || echo "unknown")
[[ "$VER" == "4.2.1" ]] \
&& log_crit "plain-crypto-js@4.2.1 INSTALLED in node_modules — active dropper" \
|| log_warn "plain-crypto-js@${VER} in node_modules — not 4.2.1 but investigate"
fi
# npm cache
CACHE_DIR=$(npm config get cache 2>/dev/null || true)
if [[ -n "$CACHE_DIR" && -d "$CACHE_DIR" ]]; then
find "$CACHE_DIR" -name "plain-crypto-js-4.2.1*.tgz" 2>/dev/null | grep -q . \
&& log_crit "plain-crypto-js@4.2.1 in npm cache — dropper was downloaded" \
|| log_ok "plain-crypto-js@4.2.1 not in npm cache"
fi
# ── 2. RAT Artifacts ──────────────────────────────────────────────────────────
section "2/5 RAT ARTIFACT CHECK"
OS="$(uname -s)"
case "$OS" in
Darwin)
RAT="/Library/Caches/com.apple.act.mond"
if [[ -f "$RAT" ]]; then
log_crit "macOS RAT binary: $RAT — ACTIVE INFECTION"
echo " $(ls -la "$RAT") SHA256: $(shasum -a 256 "$RAT" | awk '{print $1}')"
else
log_ok "macOS RAT binary not present"
fi
SCPT=$(find /tmp -maxdepth 1 -name "*.scpt" 2>/dev/null | wc -l | tr -d ' ')
[[ "$SCPT" -gt 0 ]] \
&& log_crit "$SCPT AppleScript .scpt file(s) in /tmp — execution artifact" \
|| log_ok "No .scpt artifacts in /tmp"
log show --predicate 'eventMessage contains "sfrclak"' --last 7d 2>/dev/null | grep -q 'sfrclak' \
&& log_crit "C2 domain 'sfrclak.com' in macOS unified logs — CONFIRMED C2 CONTACT" \
|| log_ok "No C2 in macOS unified logs"
;;
Linux)
if [[ -f "/tmp/ld.py" ]]; then
log_crit "Linux Python RAT: /tmp/ld.py — ACTIVE INFECTION"
echo " $(ls -la /tmp/ld.py) SHA256: $(sha256sum /tmp/ld.py | awk '{print $1}')"
else
log_ok "Linux RAT not present at /tmp/ld.py"
fi
;;
MINGW*|MSYS*|CYGWIN*)
[[ -f "C:/ProgramData/wt.exe" ]] \
&& log_crit "Windows RAT binary: C:/ProgramData/wt.exe — ACTIVE INFECTION" \
|| log_ok "Windows RAT binary not present"
;;
*)
log_warn "Unknown OS ($OS) — manual check required"
;;
esac
# ── 3. Network / C2 ───────────────────────────────────────────────────────────
section "3/5 NETWORK & C2 CHECKS"
C2_FOUND=0
for lf in /var/log/syslog /var/log/messages /var/log/auth.log \
/var/log/nginx/access.log /var/log/ufw.log; do
[[ -r "$lf" ]] || continue
grep -qiE "(sfrclak|142\.11\.206\.73)" "$lf" 2>/dev/null \
&& { log_crit "C2 indicator in $lf"; C2_FOUND=1; } \
|| log_ok "$lf clean"
done
for hist in ~/.bash_history ~/.zsh_history; do
[[ -r "$hist" ]] || continue
grep -qiE "(sfrclak|142\.11\.206\.73)" "$hist" 2>/dev/null \
&& log_crit "C2 indicator in $hist"
done
if command -v dig &>/dev/null; then
RESOLVED=$(dig +short sfrclak.com 2>/dev/null | head -1)
[[ -n "$RESOLVED" ]] \
&& log_warn "C2 domain still resolves to $RESOLVED — block at DNS level" \
|| log_ok "C2 domain does not resolve"
fi
grep -q "sfrclak.com" /etc/hosts 2>/dev/null \
&& log_ok "C2 domain already in /etc/hosts (blocked)" \
|| log_warn "C2 domain not blocked in /etc/hosts — run remediate-axios.sh"
# ── 4. npm Environment ────────────────────────────────────────────────────────
section "4/5 NPM ENVIRONMENT AUDIT"
if command -v npm &>/dev/null; then
npm list -g --depth=0 2>/dev/null | grep -qE 'plain-crypto-js|@shadanai|@qqbrowser' \
&& log_crit "Malicious package in npm globals" \
|| log_ok "npm globals clean"
NPM_TOKEN=$(npm config get //registry.npmjs.org/:_authToken 2>/dev/null || true)
[[ -n "$NPM_TOKEN" && "$NPM_TOKEN" != "undefined" ]] \
&& log_warn "npm auth token configured — verify it is short-lived/scoped"
for npmrc in ~/.npmrc ./.npmrc; do
[[ -r "$npmrc" ]] || continue
grep -qiE "(ifstap|nrwise)@proton\.me" "$npmrc" 2>/dev/null \
&& log_crit "Attacker email in $npmrc — registry config may be tampered" \
|| log_ok "$npmrc clean"
done
if [[ -d ".github/workflows" ]]; then
if grep -rqE "npm install|npm ci" .github/workflows/ 2>/dev/null; then
grep -rqE "\-\-ignore-scripts" .github/workflows/ 2>/dev/null \
&& log_ok "CI uses --ignore-scripts" \
|| log_warn "CI npm install without --ignore-scripts — consider adding it"
fi
fi
else
log_warn "npm not in PATH — skipping npm checks"
fi
# ── 5. Credential Surface ─────────────────────────────────────────────────────
section "5/5 CREDENTIAL EXPOSURE SURFACE"
SSH_COUNT=$(find ~/.ssh -name "id_*" -not -name "*.pub" 2>/dev/null | wc -l | tr -d ' ')
[[ "$SSH_COUNT" -gt 0 ]] && log_warn "$SSH_COUNT SSH private key(s) — rotate if compromised"
[[ -r ~/.aws/credentials ]] && log_warn "AWS credentials at ~/.aws/credentials — rotate if compromised"
[[ -d ~/.config/gcloud ]] && log_warn "GCP credentials at ~/.config/gcloud — rotate if compromised"
[[ -d ~/.azure ]] && log_warn "Azure credentials at ~/.azure — rotate if compromised"
ENV_COUNT=$(find . -maxdepth 4 -name ".env*" -not -path "*/node_modules/*" 2>/dev/null | wc -l | tr -d ' ')
[[ "$ENV_COUNT" -gt 0 ]] && log_warn "$ENV_COUNT .env file(s) found — rotate all secrets if compromised"
# ── Summary ───────────────────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD} DETECTION SUMMARY${RESET}"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
for f in "${FINDINGS[@]}"; do
[[ "$f" == CRITICAL* ]] \
&& echo -e " ${RED}● $f${RESET}" \
|| echo -e " ${YELLOW}● $f${RESET}"
done
echo ""
if [[ $COMPROMISED -eq 1 ]]; then
echo -e "${RED}${BOLD} VERDICT: ⚠ CRITICAL IOCs DETECTED — ASSUME COMPROMISED${RESET}"
echo " → Isolate machine · Run remediate-axios.sh · Rotate ALL credentials · Rebuild from snapshot"
elif [[ $WARNINGS -gt 0 ]]; then
echo -e "${YELLOW}${BOLD} VERDICT: ⚠ WARNINGS — Manual review recommended${RESET}"
else
echo -e "${GREEN}${BOLD} VERDICT: ✓ NO IOCs DETECTED${RESET}"
echo " Note: RAT self-deletes. If you ran npm install during 2026-03-31 00:21–03:15 UTC, treat as potentially compromised regardless."
fi
echo " $(date -u '+%Y-%m-%d %H:%M:%S UTC') | TI-AXIOS-20260331"
exit $COMPROMISEDSearches historical logs for C2 traffic. Run independently from detection.
#!/usr/bin/env bash
# network-hunt.sh — TI-AXIOS-20260331
# Usage: chmod +x network-hunt.sh && ./network-hunt.sh [--days N]
set -uo pipefail
LOOKBACK_DAYS=7
while [[ $# -gt 0 ]]; do
case $1 in --days) LOOKBACK_DAYS="$2"; shift 2 ;; *) shift ;; esac
done
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; RESET='\033[0m'
HITS=0
REPORT="c2-hunt-$(date +%Y%m%d-%H%M%S).txt"
PATTERN="sfrclak|142\.11\.206\.73"
log() { echo -e "$1" | tee -a "$REPORT"; }
log_hit() { echo -e " ${RED}✗ HIT:${RESET} $1" | tee -a "$REPORT"; HITS=$((HITS+1)); }
log_ok() { echo -e " ${GREEN}✓${RESET} $1" | tee -a "$REPORT"; }
log_skip(){ echo -e " ${YELLOW}–${RESET} SKIP: $1" | tee -a "$REPORT"; }
section() { log "\n${CYAN}${BOLD}━━━ $1 ━━━${RESET}"; }
hunt() {
local label="$1" file="$2"
[[ -r "$file" ]] || { log_skip "$label — not readable"; return; }
local hits; hits=$(grep -iE "$PATTERN" "$file" 2>/dev/null | head -10 || true)
if [[ -n "$hits" ]]; then
log_hit "$label"
echo "$hits" | while IFS= read -r l; do log " $l"; done
else
log_ok "$label — clean"
fi
}
echo -e "${CYAN}${BOLD}"
echo " ╔══════════════════════════════════════════════════════════════╗"
echo " ║ AXIOS SUPPLY CHAIN ATTACK — C2 NETWORK HUNT ║"
echo " ║ sfrclak.com | 142.11.206.73:8000 ║"
echo " ╚══════════════════════════════════════════════════════════════╝"
echo -e "${RESET}"
log " Lookback: ${LOOKBACK_DAYS}d | Host: $(hostname) | $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
OS="$(uname -s)"
# macOS unified log
section "macOS UNIFIED LOG"
if [[ "$OS" == "Darwin" ]]; then
MACOS=$(log show --predicate 'eventMessage contains "sfrclak" OR eventMessage contains "142.11.206.73"' \
--last "${LOOKBACK_DAYS}d" 2>/dev/null | head -20 || true)
[[ -n "$MACOS" ]] && { log_hit "macOS unified log"; echo "$MACOS" | while IFS= read -r l; do log " $l"; done; } \
|| log_ok "macOS unified log — clean"
else
log_skip "Not macOS"
fi
# System logs
section "SYSTEM LOGS"
for f in /var/log/syslog /var/log/messages /var/log/kern.log /var/log/auth.log \
/var/log/ufw.log /var/log/firewall.log; do
hunt "$(basename "$f")" "$f"
done
# Compressed rotated logs
for gz in /var/log/*.gz; do
[[ -r "$gz" ]] || continue
zcat "$gz" 2>/dev/null | grep -qiE "$PATTERN" \
&& { log_hit "Rotated: $gz"; zcat "$gz" 2>/dev/null | grep -iE "$PATTERN" | head -5 | while IFS= read -r l; do log " $l"; done; }
done
# Web / proxy logs
section "WEB & PROXY LOGS"
for f in /var/log/nginx/access.log /var/log/nginx/error.log \
/var/log/apache2/access.log /var/log/httpd/access_log \
/var/log/squid/access.log /var/log/haproxy.log; do
hunt "$(basename "$f")" "$f"
done
# Shell history
section "SHELL HISTORY"
mapfile -t HISTS < <(find /root /home -maxdepth 2 \
\( -name ".bash_history" -o -name ".zsh_history" \) 2>/dev/null || true)
for h in "${HISTS[@]}"; do hunt "$h" "$h"; done
# DNS
section "DNS"
if command -v journalctl &>/dev/null; then
JDNS=$(journalctl --since="${LOOKBACK_DAYS} days ago" -u systemd-resolved 2>/dev/null | \
grep -iE "$PATTERN" | head -10 || true)
[[ -n "$JDNS" ]] \
&& { log_hit "systemd-resolved journal"; echo "$JDNS" | while IFS= read -r l; do log " $l"; done; } \
|| log_ok "systemd-resolved — clean"
fi
grep -q "sfrclak.com" /etc/hosts 2>/dev/null \
&& log_ok "/etc/hosts — C2 already blocked" \
|| log " ${YELLOW}⚠${RESET} C2 not in /etc/hosts — run remediate-axios.sh"
# Zeek / Suricata
section "ZEEK / SURICATA"
for f in /var/log/zeek/dns.log /var/log/zeek/conn.log \
/opt/zeek/logs/current/dns.log \
/var/log/suricata/fast.log /var/log/suricata/eve.json; do
hunt "$(basename "$f")" "$f"
done
# GitHub Actions runner
section "CI/CD RUNNER"
for d in ~/.github-runner /opt/actions-runner /home/runner /var/lib/github-runner; do
[[ -d "$d/_diag" ]] || continue
RHITS=$(grep -riE "$PATTERN" "$d/_diag/" 2>/dev/null | head -10 || true)
[[ -n "$RHITS" ]] \
&& { log_hit "GitHub Actions runner: $d/_diag"; echo "$RHITS" | while IFS= read -r l; do log " $l"; done; } \
|| log_ok "GitHub Actions runner — clean"
done
hunt "GitLab runner" "/var/log/gitlab-runner/gitlab-runner.log"
# npm debug logs
section "npm DEBUG LOGS"
NPM_LOG="${HOME}/.npm/_logs"
if [[ -d "$NPM_LOG" ]]; then
NHITS=$(grep -riE "(plain-crypto-js|axios.*1\.14\.1|sfrclak)" "$NPM_LOG/" 2>/dev/null | head -10 || true)
[[ -n "$NHITS" ]] \
&& { log_hit "npm debug logs"; echo "$NHITS" | while IFS= read -r l; do log " $l"; done; } \
|| log_ok "npm debug logs — clean"
else
log_skip "No npm debug logs at $NPM_LOG"
fi
# Docker
section "DOCKER"
if command -v docker &>/dev/null && docker info &>/dev/null 2>&1; then
while IFS= read -r cid; do
CNAME=$(docker inspect --format '{{.Name}}' "$cid" 2>/dev/null | sed 's|/||')
DHITS=$(docker logs --since "${LOOKBACK_DAYS}d" "$cid" 2>&1 | grep -iE "$PATTERN" | head -5 || true)
[[ -n "$DHITS" ]] && { log_hit "Docker [$CNAME]"; echo "$DHITS" | while IFS= read -r l; do log " $l"; done; }
done < <(docker ps -aq 2>/dev/null | head -20)
log_ok "Docker scan complete"
else
log_skip "Docker not available"
fi
# Summary
echo ""
if [[ $HITS -gt 0 ]]; then
echo -e "${RED}${BOLD} ⚠ $HITS HIT(S) — C2 TRAFFIC CONFIRMED IN LOGS${RESET}"
echo " → Isolate · Preserve logs as evidence · Rotate credentials · Escalate to security team"
else
echo -e "${GREEN}${BOLD} ✓ NO C2 TRAFFIC FOUND in scanned logs${RESET}"
echo " Note: If npm install ran during 2026-03-31 00:21–03:15 UTC, treat as potentially compromised."
fi
log " Report saved to: $REPORT"
exit $((HITS > 0 ? 1 : 0))⚠ Isolate the machine before running this if critical IOCs were found. This reduces further exposure — it does NOT replace rebuilding from a clean snapshot.
#!/usr/bin/env bash
# remediate-axios.sh — TI-AXIOS-20260331
# Usage: chmod +x remediate-axios.sh && sudo ./remediate-axios.sh
set -uo pipefail
SAFE_1X="1.14.0"; SAFE_0X="0.30.3"
C2_DOMAIN="sfrclak.com"; C2_IP="142.11.206.73"
LOG="axios-remediation-$(date +%Y%m%d-%H%M%S).log"
DONE=()
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
log() { echo -e "$1" | tee -a "$LOG"; }
log_ok() { log " ${GREEN}✓${RESET} $1"; }
log_warn() { log " ${YELLOW}⚠${RESET} $1"; }
log_done() { log " ${GREEN}✓ DONE:${RESET} $1"; DONE+=("$1"); }
section() { log "\n${CYAN}${BOLD}━━━ $1 ━━━${RESET}"; }
confirm() { read -r -p "$(echo -e " ${YELLOW}?${RESET} $1 [y/N] ")" r; [[ "$r" =~ ^[Yy]$ ]]; }
HAVE_SUDO=false
{ [[ $EUID -eq 0 ]] || sudo -n true 2>/dev/null; } && HAVE_SUDO=true
echo -e "${RED}${BOLD}"
echo " ╔══════════════════════════════════════════════════════════════╗"
echo " ║ AXIOS SUPPLY CHAIN ATTACK — REMEDIATION ║"
echo " ║ TI-AXIOS-20260331 | GHSA-fw8c-xr5c-95f9 | MAL-2026-2306 ║"
echo " ╚══════════════════════════════════════════════════════════════╝"
echo -e "${RESET}"
echo " This script does NOT replace rotating credentials or rebuilding a"
echo " confirmed-compromised machine from a clean snapshot."
echo ""
confirm "I understand — proceed with remediation?" || { echo " Aborted."; exit 0; }
OS="$(uname -s)"
# ── Step 1: Delete RAT Artifacts ──────────────────────────────────────────────
section "STEP 1/6 DELETE RAT ARTIFACTS"
case "$OS" in
Darwin)
RAT="/Library/Caches/com.apple.act.mond"
if [[ -f "$RAT" ]]; then
log " Found: $RAT SHA256: $(shasum -a 256 "$RAT" | awk '{print $1}')"
confirm "Delete $RAT?" && {
pkill -f "com.apple.act.mond" 2>/dev/null || true
{ $HAVE_SUDO && sudo rm -f "$RAT" || rm -f "$RAT"; } \
&& log_done "Deleted $RAT" || log_warn "Failed — try with sudo"
}
else
log_ok "macOS RAT not present"
fi
find /tmp -maxdepth 1 -name "*.scpt" 2>/dev/null | while read -r f; do
rm -f "$f" && log_done "Deleted AppleScript artifact: $f"
done
;;
Linux)
if [[ -f "/tmp/ld.py" ]]; then
log " Found: /tmp/ld.py SHA256: $(sha256sum /tmp/ld.py | awk '{print $1}')"
pkill -f "/tmp/ld.py" 2>/dev/null || true
rm -f "/tmp/ld.py" && log_done "Deleted /tmp/ld.py" || log_warn "Failed to delete /tmp/ld.py"
else
log_ok "Linux RAT not present"
fi
;;
MINGW*|MSYS*|CYGWIN*)
[[ -f "C:/ProgramData/wt.exe" ]] \
&& log_warn "Windows RAT found — run as Admin: del \"%PROGRAMDATA%\\wt.exe\"" \
|| log_ok "Windows RAT not present"
;;
esac
# ── Step 2: Block C2 via /etc/hosts ──────────────────────────────────────────
section "STEP 2/6 BLOCK C2 INFRASTRUCTURE"
if grep -q "$C2_DOMAIN" /etc/hosts 2>/dev/null; then
log_ok "$C2_DOMAIN already blocked in /etc/hosts"
else
if $HAVE_SUDO; then
echo "0.0.0.0 $C2_DOMAIN # axios-ioc TI-AXIOS-20260331" | sudo tee -a /etc/hosts > /dev/null
log_done "Blocked $C2_DOMAIN in /etc/hosts"
else
log_warn "No sudo — add manually: echo '0.0.0.0 $C2_DOMAIN' | sudo tee -a /etc/hosts"
fi
fi
case "$OS" in
Darwin) sudo dscacheutil -flushcache 2>/dev/null; sudo killall -HUP mDNSResponder 2>/dev/null; log_done "DNS cache flushed (macOS)" ;;
Linux)
if command -v systemctl &>/dev/null && systemctl is-active --quiet systemd-resolved 2>/dev/null; then
sudo systemctl restart systemd-resolved 2>/dev/null && log_done "systemd-resolved restarted"
fi ;;
esac
log_warn "Also add firewall rules for $C2_IP:"
log_warn " Linux: sudo iptables -A OUTPUT -d $C2_IP -j DROP && sudo iptables -A INPUT -s $C2_IP -j DROP"
log_warn " macOS: sudo /sbin/ipfw add deny ip from any to $C2_IP"
# ── Step 3: Remove Malicious npm Packages ─────────────────────────────────────
section "STEP 3/6 REMOVE MALICIOUS npm PACKAGES"
if command -v npm &>/dev/null; then
for pkg in "node_modules/plain-crypto-js" "node_modules/@shadanai/openclaw" "node_modules/@qqbrowser/openclaw-qbot"; do
[[ -d "./$pkg" ]] && { rm -rf "./$pkg"; log_done "Removed $pkg"; } || true
done
npm cache clean --force 2>/dev/null || true
log_done "npm cache cleared"
if [[ -f "./package-lock.json" ]]; then
confirm "Delete package-lock.json for clean regeneration?" && {
cp ./package-lock.json "./package-lock.json.bak-$(date +%Y%m%d-%H%M%S)"
rm -f ./package-lock.json && log_done "package-lock.json removed (backup saved)"
}
fi
if [[ -d "./node_modules" ]]; then
confirm "Delete node_modules for full clean reinstall? (recommended)" && {
rm -rf ./node_modules && log_done "node_modules deleted"
}
fi
else
log_warn "npm not found — skipping npm steps"
fi
# ── Step 4: Pin axios to Safe Version ─────────────────────────────────────────
section "STEP 4/6 PIN axios TO SAFE VERSION"
if [[ -f "./package.json" ]]; then
CURRENT=$(node -e "try{const p=require('./package.json');console.log(p.dependencies?.axios||p.devDependencies?.axios||'')}catch(e){}" 2>/dev/null || true)
log " Current axios in package.json: ${CURRENT:-not found}"
if [[ "$CURRENT" == *"1.14.1"* || "$CURRENT" == *"0.30.4"* ]]; then
SAFE_PIN=$([[ "$CURRENT" == *"0."* ]] && echo "$SAFE_0X" || echo "$SAFE_1X")
node -e "
const fs=require('fs');
const pkg=JSON.parse(fs.readFileSync('./package.json','utf8'));
const safe='$SAFE_PIN';
if(pkg.dependencies?.axios) pkg.dependencies.axios=safe;
if(pkg.devDependencies?.axios) pkg.devDependencies.axios=safe;
fs.writeFileSync('./package.json',JSON.stringify(pkg,null,2)+'\n');
" 2>/dev/null && log_done "axios pinned to $SAFE_PIN in package.json" \
|| log_warn "Could not auto-update package.json — set axios to $SAFE_PIN manually"
else
log_ok "axios version not flagged — recommended safe: axios@$SAFE_1X or axios@$SAFE_0X"
fi
if [[ ! -d "./node_modules" ]]; then
confirm "Run npm install to regenerate clean node_modules?" && {
npm install 2>&1 | tee -a "$LOG"
npm ls plain-crypto-js 2>/dev/null | grep -q plain-crypto-js \
&& log_warn "plain-crypto-js still in tree — investigate" \
|| log_done "npm install complete — plain-crypto-js absent"
}
fi
else
log_warn "No package.json in current directory"
fi
# ── Step 5: Credential Rotation Checklist ─────────────────────────────────────
section "STEP 5/6 CREDENTIAL ROTATION CHECKLIST"
echo "
If any IOC was found, rotate ALL of the following immediately:
☐ npm token — npmjs.com → Settings → Access Tokens → Revoke
☐ SSH keys — generate new key pair, update authorized_keys everywhere
☐ AWS credentials — rotate in IAM console (~/.aws/credentials)
☐ GCP credentials — revoke in Cloud Console (~/.config/gcloud)
☐ Azure tokens — revoke in Azure Portal (~/.azure)
☐ GitHub tokens — Settings → Developer settings → Personal access tokens
☐ .env secrets — rotate all values and redeploy
☐ CI/CD secrets — GitHub Actions / GitLab CI environment variables
☐ Database passwords accessible from this machine
☐ Browser credential stores (Chrome/Firefox)
"
# ── Step 6: CI Hardening ──────────────────────────────────────────────────────
section "STEP 6/6 CI/CD HARDENING"
echo "
Apply these to prevent future supply chain attacks:
1. Exact version pins (remove ^ and ~ from package.json):
sed -i 's/\"\\^/\"/g; s/\"~/\"/g' package.json
2. Run npm ci --ignore-scripts in CI pipelines
3. Enable npm Trusted Publishing (OIDC) — disable long-lived CLI tokens:
https://docs.npmjs.com/generating-provenance-statements
4. Add StepSecurity Harden-Runner for CI egress monitoring:
https://github.com/step-security/harden-runner
5. Add supply chain scanning to pipelines:
npx socket npm install (Socket)
snyk test (Snyk)
"
# ── Summary ───────────────────────────────────────────────────────────────────
section "REMEDIATION COMPLETE"
[[ ${#DONE[@]} -gt 0 ]] && { log " Actions taken:"; for a in "${DONE[@]}"; do log " ✓ $a"; done; }
echo ""
log_warn "This script reduces exposure — rebuild from a clean snapshot if IOCs were confirmed."
log " Audit log: ${BOLD}$LOG${RESET} | TI-AXIOS-20260331 | $(date -u '+%Y-%m-%d %H:%M:%S UTC')"Save as
axios-ioc.yarand run:yara axios-ioc.yar ./node_modules -r
Install:brew install yara(macOS) ·sudo apt install yara(Linux)
/*
* axios-ioc.yar — Axios npm Supply Chain Attack
* TI-AXIOS-20260331 | GHSA-fw8c-xr5c-95f9 | MAL-2026-2306
*/
rule axios_dropper_xor_key {
meta:
description = "Detects XOR key from axios supply chain dropper — high confidence"
severity = "CRITICAL"
confidence = "HIGH"
strings:
$xor_key = "OrDeR_7077" ascii wide
$entry_fn = "_entry(" ascii
$self_delete = "unlinkSync" ascii
condition:
$xor_key or ($entry_fn and $self_delete)
}
rule axios_dropper_c2 {
meta:
description = "Detects C2 domain/IP from the axios supply chain RAT"
severity = "CRITICAL"
confidence = "HIGH"
strings:
$domain = "sfrclak.com" ascii wide nocase
$ip = "142.11.206.73" ascii wide
$hostport= "sfrclak.com:8000" ascii wide nocase
condition:
any of them
}
rule axios_attacker_emails {
meta:
description = "Attacker-controlled ProtonMail addresses used in npm registry"
severity = "HIGH"
confidence = "MEDIUM"
strings:
$email1 = "ifstap@proton.me" ascii wide nocase
$email2 = "nrwise@proton.me" ascii wide nocase
condition:
any of them
}
rule axios_macos_rat {
meta:
description = "macOS Mach-O RAT — dropped at /Library/Caches/com.apple.act.mond"
severity = "CRITICAL"
platform = "macOS"
strings:
$c2 = "sfrclak.com" ascii nocase
$persist = "com.apple.act.mond" ascii
$osascript= "osascript" ascii
$nlohmann = "nlohmann" ascii
condition:
(uint32(0) == 0xCAFEBABE or uint32(0) == 0xFEEDFACF) and
($c2 or $persist or ($osascript and $nlohmann))
}
rule axios_linux_python_rat {
meta:
description = "Linux Python RAT — dropped at /tmp/ld.py"
severity = "CRITICAL"
platform = "Linux"
strings:
$c2 = "sfrclak.com" ascii nocase
$ip = "142.11.206.73" ascii
$aws = "~/.aws/credentials" ascii
$ssh = "~/.ssh/id_" ascii
$ci = "GITHUB_TOKEN" ascii
$gcp = "~/.config/gcloud" ascii
condition:
($c2 or $ip) and (2 of ($aws, $ssh, $ci, $gcp))
}
rule axios_windows_powershell_rat {
meta:
description = "Windows PowerShell RAT — dropped at %PROGRAMDATA%\\wt.exe"
severity = "CRITICAL"
platform = "Windows"
strings:
$c2 = "sfrclak.com" ascii wide nocase
$ip = "142.11.206.73" ascii wide
$ps_byp = "-ExecutionPolicy Bypass" ascii wide nocase
$ps_enc = "-EncodedCommand" ascii wide nocase
$ssh = "\\.ssh\\id_" ascii wide nocase
$cred = "CredEnumerate" ascii wide
condition:
($c2 or $ip) and (2 of ($ps_byp, $ps_enc, $ssh, $cred))
}
rule axios_lockfile_poisoning {
meta:
description = "Detects malicious packages in npm lockfiles"
severity = "CRITICAL"
confidence = "HIGH"
targets = "package-lock.json, yarn.lock, pnpm-lock.yaml"
strings:
$dropper = "plain-crypto-js" ascii
$bad_ver = "4.2.1" ascii
$axios_1 = "axios@1.14.1" ascii
$axios_0 = "axios@0.30.4" ascii
$related1 = "@shadanai/openclaw" ascii
$related2 = "@qqbrowser/openclaw-qbot" ascii
condition:
($dropper and $bad_ver) or $axios_1 or $axios_0 or $related1 or $related2
}# ── Check lockfiles ───────────────────────────────────────────────────────────
grep -r 'plain-crypto-js' package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
grep -rE 'axios@(1\.14\.1|0\.30\.4)' package-lock.json yarn.lock 2>/dev/null
# ── macOS ─────────────────────────────────────────────────────────────────────
ls -la /Library/Caches/com.apple.act.mond 2>/dev/null
find /tmp -maxdepth 1 -name "*.scpt" 2>/dev/null
log show --predicate 'eventMessage contains "sfrclak"' --last 7d
# ── Linux ─────────────────────────────────────────────────────────────────────
ls -la /tmp/ld.py 2>/dev/null
# ── Windows (PowerShell) ──────────────────────────────────────────────────────
Get-Item "$env:ProgramData\wt.exe" -ErrorAction SilentlyContinue
# ── Network logs (all platforms) ─────────────────────────────────────────────
grep -i 'sfrclak' /var/log/*.log 2>/dev/null
grep '142.11.206.73' /var/log/*.log 2>/dev/null
# ── npm verify (after remediation) ───────────────────────────────────────────
npm ls plain-crypto-js # should return empty| Step | Action | Priority |
|---|---|---|
| 1 | Isolate affected machine from network | 🔴 CRITICAL |
| 2 | Rotate ALL credentials (SSH, API keys, cloud tokens, .env) | 🔴 CRITICAL |
| 3 | Rebuild from clean snapshot — do not attempt in-place cleanup | 🔴 CRITICAL |
| 4 | Pin to axios@1.14.0 or axios@0.30.3 across all projects |
🔴 CRITICAL |
| 5 | Block sfrclak.com and 142.11.206.73 at firewall + DNS |
🟠 HIGH |
| 6 | Audit CI/CD pipeline runs from 2026-03-31 UTC | 🟠 HIGH |
| 7 | Remove plain-crypto-js from all lockfiles and node_modules |
🟠 HIGH |
| 8 | Enable npm Trusted Publishing (OIDC) — revoke long-lived tokens | 🟡 MEDIUM |
| 9 | Deploy CI egress monitoring (StepSecurity Harden-Runner) | 🟡 MEDIUM |
| 10 | Pin critical dependencies with exact versions (no ^ or ~) |
🟡 MEDIUM |
- StepSecurity — primary detection source
- Socket Security — automated scanner report
- Snyk — SNYK-JS-PLAINCRYPTOJS-15850652
- OX Security — Mach-O reverse engineering
- Wiz Research — cloud environment exposure
- Vercel — platform impact & remediation
- Org-wide repo scanner
Report ID: TI-AXIOS-20260331 · Classification: TLP:AMBER · Source: Transilience AI Threat Intelligence