Skip to content

Instantly share code, notes, and snippets.

@rkreddyp
Created April 1, 2026 02:20
Show Gist options
  • Select an option

  • Save rkreddyp/4259ed45f1ca98c19bbeeda0f39148fe to your computer and use it in GitHub Desktop.

Select an option

Save rkreddyp/4259ed45f1ca98c19bbeeda0f39148fe to your computer and use it in GitHub Desktop.

🚨 Axios npm Supply Chain Attack — IOC Detection & Remediation

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


What Happened

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.


Indicators of Compromise

Network

Indicator Type Severity
sfrclak.com C2 Domain 🔴 CRITICAL
142.11.206.73 C2 IP 🔴 CRITICAL
sfrclak.com:8000 C2 Host:Port 🔴 CRITICAL

Filesystem

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

npm / Registry

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

Malware Artifacts

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)

Script 1 — IOC Detection

Run this first. Exits 0 (clean) or 1 (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 $COMPROMISED

Script 2 — Network Log Hunt

Searches 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))

Script 3 — Remediation & Deletion

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')"

YARA Rules

Save as axios-ioc.yar and 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
}

Quick Reference — Detection Commands

# ── 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

Remediation Summary

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

References


Report ID: TI-AXIOS-20260331 · Classification: TLP:AMBER · Source: Transilience AI Threat Intelligence

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment