Skip to content

Instantly share code, notes, and snippets.

@MarkKropf
Created March 31, 2026 14:31
Show Gist options
  • Select an option

  • Save MarkKropf/c72344a9dec60081ee8cf0ffb1de8af8 to your computer and use it in GitHub Desktop.

Select an option

Save MarkKropf/c72344a9dec60081ee8cf0ffb1de8af8 to your computer and use it in GitHub Desktop.
Axios Supply Chain Compromise Scanner
#!/usr/bin/env bash
# ============================================================================
# Axios Supply Chain Compromise Scanner
# Author: Mark Kropf
# Date: 2026-03-31
# Ref: https://socket.dev/blog/axios-npm-package-compromised
# https://www.aikido.dev/blog/axios-npm-compromised-maintainer-hijacked-rat
#
# Scans for:
# 1. Malicious axios versions (1.14.1, 0.30.4)
# 2. Malicious dependency packages (plain-crypto-js, @shadanai/openclaw, @qqbrowser/openclaw-qbot)
# 3. macOS RAT binary: /Library/Caches/com.apple.act.mond
# 4. Dropper artifacts ($TMPDIR/6202033, setup.js postinstall hooks)
# 5. C2 network indicators (sfrclak.com, 142.11.206.73)
# 6. All non-malicious axios installations for awareness
#
# Usage: sudo ./scan-axios-compromise.sh [scan_root ...]
# Defaults to scanning / plus all /Volumes/* mounts
# sudo recommended for full filesystem access
# ============================================================================
set -euo pipefail
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
INFECTED=0
WARNINGS=0
AXIOS_FOUND=0
SCAN_START=$(date +%s)
log_critical() { echo -e "${RED}[CRITICAL]${NC} $*"; ((INFECTED++)); }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $*"; ((WARNINGS++)); }
log_info() { echo -e "${CYAN}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
log_section() {
local now elapsed
now=$(date +%s)
elapsed=$(( now - SCAN_START ))
echo -e "\n${BOLD}=== $* === ${CYAN}[${elapsed}s]${NC}"
}
# --- Known IOCs ---
MALICIOUS_AXIOS_VERSIONS=("1.14.1" "0.30.4")
MALICIOUS_PACKAGES=("plain-crypto-js" "@shadanai/openclaw" "@qqbrowser/openclaw-qbot")
C2_DOMAIN="sfrclak.com"
C2_IP="142.11.206.73"
MACOS_RAT_PATH="/Library/Caches/com.apple.act.mond"
MACOS_RAT_SHA256="92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a"
LINUX_PAYLOAD_SHA256="fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf"
# Store our own PID to exclude from process checks
SCANNER_PID=$$
# Determine scan roots
SCAN_ROOTS=()
if [[ $# -gt 0 ]]; then
SCAN_ROOTS=("$@")
else
SCAN_ROOTS=("/")
if [[ -d /Volumes ]]; then
for vol in /Volumes/*/; do
[[ -d "$vol" ]] && SCAN_ROOTS+=("${vol%/}")
done
fi
fi
echo -e "${BOLD}============================================================${NC}"
echo -e "${BOLD} Axios Supply Chain Compromise Scanner${NC}"
echo -e "${BOLD} $(date)${NC}"
echo -e "${BOLD}============================================================${NC}"
echo ""
echo "Scan roots: ${SCAN_ROOTS[*]}"
echo "Scanner PID: $SCANNER_PID"
echo ""
# ============================================================================
# PHASE 1: Check for macOS RAT binary (fast - direct path checks + targeted find)
# ============================================================================
log_section "Phase 1: Checking for macOS RAT binary"
# Helper: check if the RAT process is genuinely running (not our own scanner)
check_rat_process() {
# Use [c] bracket trick to exclude grep, then filter out find/scanner processes
local real_hits
real_hits=$(ps aux | grep "[c]om.apple.act.mond" \
| grep -v -E "find |grep |pgrep |bash.*(scan|axios)" \
| grep -v "$$" || true)
if [[ -n "$real_hits" ]]; then
log_critical "RAT PROCESS com.apple.act.mond IS RUNNING!"
echo "$real_hits"
return 0
fi
return 1
}
check_rat_path() {
local rat_path="$1"
if [[ -f "$rat_path" ]]; then
log_critical "RAT BINARY FOUND: $rat_path"
ls -la "$rat_path"
local sha
sha=$(shasum -a 256 "$rat_path" 2>/dev/null | awk '{print $1}')
if [[ "$sha" == "$MACOS_RAT_SHA256" ]]; then
log_critical "SHA256 MATCHES known malicious RAT: $sha"
else
log_critical "SHA256: $sha (differs from known IOC but file exists at malicious path)"
fi
check_rat_process || true
return 1
fi
return 0
}
# Check standard path first (instant)
check_rat_path "$MACOS_RAT_PATH" || true
# Check across all scan roots for Library/Caches variant (instant per root)
for root in "${SCAN_ROOTS[@]}"; do
rat_candidate="${root}/Library/Caches/com.apple.act.mond"
[[ "$rat_candidate" == "$MACOS_RAT_PATH" ]] && continue
check_rat_path "$rat_candidate" || true
done
# Targeted find - only search Library/Caches directories, not entire tree
log_info "Searching Library/Caches dirs for com.apple.act.mond..."
for root in "${SCAN_ROOTS[@]}"; do
while IFS= read -r -d '' match; do
[[ "$match" == "$MACOS_RAT_PATH" ]] && continue
log_critical "RAT binary found at unexpected location: $match"
ls -la "$match"
shasum -a 256 "$match" 2>/dev/null
done < <(find "$root" -path "*/Library/Caches/com.apple.act.mond" -print0 2>/dev/null)
done
if [[ $INFECTED -eq 0 ]]; then
log_ok "No RAT binary found"
fi
# Process check (separate from file check)
check_rat_process || log_ok "No com.apple.act.mond process running"
# ============================================================================
# PHASE 2: Check for dropper artifacts (fast - direct path checks)
# ============================================================================
log_section "Phase 2: Checking for dropper/temp artifacts"
TMPDIR_ACTUAL="${TMPDIR:-/tmp}"
if [[ -f "${TMPDIR_ACTUAL}/6202033" ]]; then
log_critical "Dropper artifact found: ${TMPDIR_ACTUAL}/6202033"
ls -la "${TMPDIR_ACTUAL}/6202033"
file "${TMPDIR_ACTUAL}/6202033" 2>/dev/null
else
log_ok "No dropper at ${TMPDIR_ACTUAL}/6202033"
fi
if [[ -f "/tmp/ld.py" ]]; then
log_critical "Linux payload found: /tmp/ld.py"
sha=$(shasum -a 256 "/tmp/ld.py" 2>/dev/null | awk '{print $1}')
if [[ "$sha" == "$LINUX_PAYLOAD_SHA256" ]]; then
log_critical "SHA256 matches known malicious Linux payload"
fi
else
log_ok "No Linux payload at /tmp/ld.py"
fi
# Windows artifacts on mounted volumes (depth-limited)
for root in "${SCAN_ROOTS[@]}"; do
for f in $(find "$root" -maxdepth 5 \( -name "6202033.vbs" -o -name "6202033.ps1" \) 2>/dev/null); do
log_critical "Dropper artifact found: $f"
done
done
# ============================================================================
# PHASE 3: Scan for malicious and all axios installations (optimized)
# Uses a single find pass with -prune to skip deep .pnpm virtual stores
# ============================================================================
log_section "Phase 3: Scanning for axios installations (node_modules)"
MALICIOUS_INSTALLS=()
SAFE_INSTALLS=()
scan_package_json() {
local pkg_json="$1"
local pkg_dir
pkg_dir=$(dirname "$pkg_json")
local name version
# Fast extraction: read first 5 lines to get name/version (avoids reading full file)
name=$(sed -n '/"name"/{s/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p;q;}' "$pkg_json" 2>/dev/null || echo "")
version=$(sed -n '/"version"/{s/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p;q;}' "$pkg_json" 2>/dev/null || echo "")
if [[ "$name" == "axios" ]]; then
((AXIOS_FOUND++))
local is_malicious=false
for mv in "${MALICIOUS_AXIOS_VERSIONS[@]}"; do
if [[ "$version" == "$mv" ]]; then
is_malicious=true
break
fi
done
if $is_malicious; then
log_critical "MALICIOUS axios@${version} found: $pkg_dir"
MALICIOUS_INSTALLS+=("$pkg_dir (axios@${version})")
if [[ -f "$pkg_dir/setup.js" ]]; then
log_critical " setup.js (postinstall dropper) present!"
if grep -q "sfrclak\|6202033\|plain-crypto" "$pkg_dir/setup.js" 2>/dev/null; then
log_critical " setup.js contains known malicious indicators!"
fi
fi
if grep -q '"postinstall"' "$pkg_json" 2>/dev/null; then
log_critical " package.json has postinstall script!"
grep '"postinstall"' "$pkg_json"
fi
else
SAFE_INSTALLS+=("$pkg_dir (axios@${version})")
fi
fi
}
for root in "${SCAN_ROOTS[@]}"; do
log_info "Scanning $root for node_modules/axios ..."
# Single find pass: look for axios package.json, skip .Trash/System/.cache
while IFS= read -r -d '' pkg_json; do
scan_package_json "$pkg_json"
done < <(find "$root" \
\( -name ".Trash" -o -name "System" -o -name ".cache" \) -prune -o \
-path "*/node_modules/axios/package.json" -print0 2>/dev/null)
# Malicious companion packages - targeted find for each
log_info "Scanning $root for malicious companion packages..."
for mpkg in "${MALICIOUS_PACKAGES[@]}"; do
while IFS= read -r -d '' pkg_json; do
local name
name=$(sed -n '/"name"/{s/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p;q;}' "$pkg_json" 2>/dev/null || echo "")
if [[ "$name" == "$mpkg" ]]; then
local ver
ver=$(sed -n '/"version"/{s/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p;q;}' "$pkg_json" 2>/dev/null || echo "")
log_critical "MALICIOUS PACKAGE ${mpkg}@${ver} found: $(dirname "$pkg_json")"
fi
done < <(find "$root" \
\( -name ".Trash" -o -name "System" -o -name ".cache" \) -prune -o \
-path "*/node_modules/${mpkg}/package.json" -print0 2>/dev/null)
done
done
log_info "Found $AXIOS_FOUND axios installations"
# ============================================================================
# PHASE 4: Check package manager caches (runs once, not per-root)
# ============================================================================
log_section "Phase 4: Checking package manager caches"
NPM_CACHE="${HOME}/.npm"
YARN_CACHE="${HOME}/.yarn/cache"
PNPM_STORE="${HOME}/.local/share/pnpm/store"
PNPM_STORE_ALT="${HOME}/Library/pnpm/store"
check_cache_dir() {
local cache_dir="$1"
local label="$2"
if [[ ! -d "$cache_dir" ]]; then
log_info "$label not found at $cache_dir (skipping)"
return
fi
log_info "Checking $label ($cache_dir)..."
# Targeted: look for tarballs/metadata with malicious package names
local found=0
# Check for plain-crypto-js
local crypto_hits
crypto_hits=$(find "$cache_dir" -type f -name "*plain-crypto*" 2>/dev/null || true)
if [[ -n "$crypto_hits" ]]; then
log_critical "Malicious package plain-crypto-js found in $label cache:"
echo "$crypto_hits"
((found++))
fi
# Check for malicious scoped openclaw packages only
local openclaw_hits
openclaw_hits=$(find "$cache_dir" -type f \( -name "*shadanai*openclaw*" -o -name "*qqbrowser*openclaw*" -o -name "*openclaw-qbot*" \) 2>/dev/null || true)
if [[ -n "$openclaw_hits" ]]; then
log_critical "Malicious openclaw package found in $label cache:"
echo "$openclaw_hits"
((found++))
fi
# Check for malicious axios tarballs by name pattern (much faster than grep-all-json)
local axios_tarballs
axios_tarballs=$(find "$cache_dir" -type f \( -name "*axios-1.14.1*" -o -name "*axios-0.30.4*" \) 2>/dev/null || true)
if [[ -n "$axios_tarballs" ]]; then
log_critical "Malicious axios tarball found in $label cache:"
echo "$axios_tarballs"
((found++))
fi
# Check pnpm index files for malicious axios versions (pnpm stores metadata as json)
if [[ -d "$cache_dir" && "$label" == *"pnpm"* ]]; then
local pnpm_idx
pnpm_idx=$(find "$cache_dir" -type f \( -name "*axios@1.14.1*" -o -name "*axios@0.30.4*" \) 2>/dev/null || true)
if [[ -n "$pnpm_idx" ]]; then
log_critical "Malicious axios version in pnpm index: $pnpm_idx"
((found++))
fi
# Check for scoped openclaw in pnpm index
local pnpm_openclaw
pnpm_openclaw=$(find "$cache_dir" -type f \( -name "*@shadanai*" -o -name "*@qqbrowser*" \) 2>/dev/null || true)
if [[ -n "$pnpm_openclaw" ]]; then
log_critical "Malicious scoped package in pnpm index:"
echo "$pnpm_openclaw"
((found++))
fi
fi
# For npm cache: check _cacache for malicious shasums in content-addressed store
if [[ "$label" == "npm cache" && -d "$cache_dir/_cacache" ]]; then
for sha in "2553649f2322049666871cea80a5d0d6adc700ca" "d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71" "07d889e2dadce6f3910dcbc253317d28ca61c766"; do
if find "$cache_dir/_cacache" -name "*${sha}*" 2>/dev/null | grep -q .; then
log_critical "Malicious shasum $sha found in npm content-addressed cache"
((found++))
fi
done
fi
if [[ $found -eq 0 ]]; then
log_ok "$label clean"
fi
}
check_cache_dir "$NPM_CACHE" "npm cache"
check_cache_dir "$YARN_CACHE" "yarn cache"
check_cache_dir "$PNPM_STORE" "pnpm store"
check_cache_dir "$PNPM_STORE_ALT" "pnpm store (alt)"
# Check npm global
GLOBAL_NM=$(npm root -g 2>/dev/null || echo "")
if [[ -n "$GLOBAL_NM" && -d "$GLOBAL_NM/axios" ]]; then
log_warning "axios installed globally at $GLOBAL_NM/axios"
scan_package_json "$GLOBAL_NM/axios/package.json"
fi
# ============================================================================
# PHASE 5: Check lock files for malicious versions/shasums
# Uses ripgrep if available (10-100x faster), falls back to grep
# ============================================================================
log_section "Phase 5: Scanning lock files for malicious versions/integrity hashes"
MALICIOUS_SHASUMS=(
"2553649f2322049666871cea80a5d0d6adc700ca" # axios@1.14.1
"d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71" # axios@0.30.4
"07d889e2dadce6f3910dcbc253317d28ca61c766" # plain-crypto-js@4.2.1
)
# Build a single combined regex for all IOCs (one grep pass per lock file)
LOCKFILE_PATTERN="axios@1\.14\.1\|axios@0\.30\.4\|axios.*1\.14\.1\|axios.*0\.30\.4"
LOCKFILE_PATTERN="${LOCKFILE_PATTERN}\|plain-crypto-js"
LOCKFILE_PATTERN="${LOCKFILE_PATTERN}\|@shadanai/openclaw\|@qqbrowser/openclaw-qbot"
LOCKFILE_PATTERN="${LOCKFILE_PATTERN}\|packages\.npm\.org"
for sha in "${MALICIOUS_SHASUMS[@]}"; do
LOCKFILE_PATTERN="${LOCKFILE_PATTERN}\|${sha}"
done
LOCKFILES_CHECKED=0
for root in "${SCAN_ROOTS[@]}"; do
while IFS= read -r -d '' lockfile; do
((LOCKFILES_CHECKED++))
# Single grep pass with combined pattern
matches=$(grep -n "$LOCKFILE_PATTERN" "$lockfile" 2>/dev/null || true)
if [[ -n "$matches" ]]; then
log_critical "IOC match in lock file: $lockfile"
echo "$matches" | head -20
fi
done < <(find "$root" \( -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" \) \
-not -path "*/node_modules/*" \
-not -path "*/.Trash/*" \
-not -path "*/System/*" \
-print0 2>/dev/null)
done
log_info "Checked $LOCKFILES_CHECKED lock files"
# ============================================================================
# PHASE 6: Network/DNS indicators (fast - no filesystem scan)
# ============================================================================
log_section "Phase 6: Checking for C2 network indicators"
if grep -q "$C2_DOMAIN" /etc/hosts 2>/dev/null; then
log_critical "/etc/hosts contains C2 domain: $C2_DOMAIN"
fi
if command -v lsof &>/dev/null; then
if lsof -i -n -P 2>/dev/null | grep -q "$C2_IP"; then
log_critical "ACTIVE CONNECTION to C2 IP $C2_IP detected!"
lsof -i -n -P 2>/dev/null | grep "$C2_IP"
else
log_ok "No active connections to C2 IP $C2_IP"
fi
fi
if command -v netstat &>/dev/null; then
if netstat -an 2>/dev/null | grep -q "$C2_IP"; then
log_critical "Network connection to C2 IP found in netstat!"
fi
fi
# C2 references in source: use ripgrep if available (massively faster), else targeted grep
# Only scan node_modules/axios dirs and top-level project files, not ALL source
log_info "Searching for C2 domain/IP in axios packages and project configs..."
C2_PATTERN="sfrclak|142\.11\.206\.73|packages\.npm\.org"
if command -v rg &>/dev/null; then
for root in "${SCAN_ROOTS[@]}"; do
# Fast: ripgrep with built-in parallelism, skip .git/dist/build/.cache
rg_hits=$(rg -l --no-messages \
--glob '!.git' --glob '!dist' --glob '!build' --glob '!.cache' --glob '!*.min.js' --glob '!*.map' \
--glob '*.{js,ts,json,mjs,cjs}' \
-e "$C2_PATTERN" \
"$root" 2>/dev/null | head -50 || true)
if [[ -n "$rg_hits" ]]; then
while IFS= read -r hit; do
log_critical "C2 indicator found in: $hit"
rg -n "$C2_PATTERN" "$hit" 2>/dev/null | head -3
done <<< "$rg_hits"
fi
done
else
# Fallback: only grep inside axios dirs and top-level configs (not all source)
for root in "${SCAN_ROOTS[@]}"; do
# Check axios package dirs specifically
while IFS= read -r -d '' axdir; do
if grep -rlq "sfrclak\|142\.11\.206\.73\|packages\.npm\.org" "$axdir" 2>/dev/null; then
log_critical "C2 indicator found in axios dir: $axdir"
fi
done < <(find "$root" -path "*/node_modules/axios" -type d -print0 2>/dev/null)
# Check top-level package.json / lock files (already covered in phase 5 but catches package.json too)
while IFS= read -r -d '' pjson; do
if grep -q "sfrclak\|142\.11\.206\.73\|packages\.npm\.org" "$pjson" 2>/dev/null; then
log_critical "C2 indicator in project config: $pjson"
fi
done < <(find "$root" -maxdepth 3 -name "package.json" -not -path "*/node_modules/*" -print0 2>/dev/null)
done
fi
# ============================================================================
# PHASE 7: Check LaunchAgents/LaunchDaemons for persistence (fast)
# ============================================================================
log_section "Phase 7: Checking for persistence mechanisms (LaunchAgents/Daemons)"
LAUNCH_DIRS=(
"/Library/LaunchAgents"
"/Library/LaunchDaemons"
"$HOME/Library/LaunchAgents"
"$HOME/Library/LaunchDaemons"
)
for ldir in "${LAUNCH_DIRS[@]}"; do
if [[ -d "$ldir" ]]; then
while IFS= read -r -d '' plist; do
if grep -q "com.apple.act.mond\|sfrclak\|6202033" "$plist" 2>/dev/null; then
log_critical "Persistence plist referencing malware: $plist"
fi
done < <(find "$ldir" -name "*.plist" -print0 2>/dev/null)
fi
done
log_ok "Persistence check complete"
# ============================================================================
# PHASE 8: Check crontabs (fast)
# ============================================================================
log_section "Phase 8: Checking crontabs"
if crontab -l 2>/dev/null | grep -q "com.apple.act.mond\|sfrclak\|6202033"; then
log_critical "Crontab contains malware reference!"
crontab -l 2>/dev/null | grep "com.apple.act.mond\|sfrclak\|6202033"
else
log_ok "No malware references in user crontab"
fi
# ============================================================================
# SUMMARY
# ============================================================================
SCAN_END=$(date +%s)
SCAN_DURATION=$(( SCAN_END - SCAN_START ))
log_section "SCAN COMPLETE - SUMMARY"
echo ""
echo "Scan roots: ${SCAN_ROOTS[*]}"
echo "Axios installs: $AXIOS_FOUND total found"
echo "Lock files: $LOCKFILES_CHECKED checked"
echo "Duration: ${SCAN_DURATION}s"
echo ""
if [[ $INFECTED -gt 0 ]]; then
echo -e "${RED}${BOLD}!!! CRITICAL: $INFECTED infection indicator(s) detected !!!${NC}"
echo ""
echo "IMMEDIATE ACTIONS REQUIRED:"
echo " 1. Disconnect from network immediately"
echo " 2. Kill any com.apple.act.mond processes: sudo pkill -f com.apple.act.mond"
echo " 3. Remove RAT binary: sudo rm -f /Library/Caches/com.apple.act.mond"
echo " 4. Remove malicious axios: rm -rf <path>/node_modules/axios && npm install"
echo " 5. Clear package caches: npm cache clean --force"
echo " 6. Audit for credential theft - rotate all secrets/tokens/keys"
echo " 7. Check git history for unauthorized commits"
echo ""
fi
if [[ $WARNINGS -gt 0 ]]; then
echo -e "${YELLOW}Warnings: $WARNINGS${NC}"
fi
if [[ ${#MALICIOUS_INSTALLS[@]} -gt 0 ]]; then
echo ""
echo -e "${RED}Malicious installations:${NC}"
for m in "${MALICIOUS_INSTALLS[@]}"; do
echo " - $m"
done
fi
if [[ ${#SAFE_INSTALLS[@]} -gt 0 ]]; then
echo ""
echo -e "${GREEN}Non-malicious axios installations (${#SAFE_INSTALLS[@]}):${NC}"
for s in "${SAFE_INSTALLS[@]}"; do
echo " - $s"
done
fi
echo ""
if [[ $INFECTED -eq 0 && $WARNINGS -eq 0 ]]; then
echo -e "${GREEN}${BOLD}System appears clean. No indicators of compromise found.${NC}"
else
echo -e "${RED}${BOLD}Review findings above and take remediation steps.${NC}"
fi
exit $INFECTED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment