Skip to content

Instantly share code, notes, and snippets.

@nathan815
Last active April 1, 2026 14:44
Show Gist options
  • Select an option

  • Save nathan815/3b2baf96f65648664c6b5bb0a19da82c to your computer and use it in GitHub Desktop.

Select an option

Save nathan815/3b2baf96f65648664c6b5bb0a19da82c to your computer and use it in GitHub Desktop.
axios@1.14.1 / axios@0.30.4 supply chain compromise — detection scripts for Windows, macOS, Linux (2026-03-31)

axios Supply Chain Compromise Detection Script

What happened?

On March 31, 2026 (UTC), the primary npm maintainer account (jasonsaayman) for the axios HTTP client was compromised. The attacker published two malicious versions:

  • axios@1.14.1 (tagged latest)
  • axios@0.30.4 (tagged legacy)

Both versions inject a hidden dependency (plain-crypto-js@4.2.1) that executes a postinstall hook dropping a cross-platform Remote Access Trojan (RAT). The malware self-destructs and replaces its own package.json to hide evidence.

Safe versions: axios@1.14.0 (1.x) and axios@0.30.3 (0.x)

How to use

Windows (PowerShell)

irm https://gist.githubusercontent.com/nathan815/3b2baf96f65648664c6b5bb0a19da82c/raw/check-axios-compromise.ps1 | iex

Or download and run:

./check-axios-compromise.ps1
# Optionally scan a specific project directory:
./check-axios-compromise.ps1 -ProjectDir "C:\projects\my-app"

macOS / Linux (Bash)

bash <(curl -fsSL https://gist.githubusercontent.com/nathan815/3b2baf96f65648664c6b5bb0a19da82c/raw/check-axios-compromise.sh)
# Optionally scan a specific project directory:
bash <(curl -fsSL https://gist.githubusercontent.com/nathan815/3b2baf96f65648664c6b5bb0a19da82c/raw/check-axios-compromise.sh) /path/to/my-app

What does it check?

  1. RAT artifacts on disk - platform-specific malware persistence files
  2. plain-crypto-js in node_modules - the malicious dependency (its mere presence = dropper executed)
  3. package-lock.json files - scans for lockfiles referencing compromised axios versions
  4. Network indicators - checks DNS resolution / hosts file for the C2 domain

Indicators of Compromise

Indicator Value
C2 domain sfrclak.com
C2 IP 142.11.206.73
C2 URL http://sfrclak.com:8000/6202033
macOS artifact /Library/Caches/com.apple.act.mond
Windows artifact %PROGRAMDATA%\wt.exe
Linux artifact /tmp/ld.py
Malicious dependency node_modules/plain-crypto-js/

References

Remediation

If compromised:

  1. Do NOT attempt to clean in place ΓÇö rebuild the system from a known-good state
  2. Rotate ALL credentials accessible from the compromised machine (npm tokens, cloud creds, SSH keys, .env files)
  3. Audit CI/CD pipelines for runs that may have installed affected versions
  4. Pin axios: "axios": "1.14.0" (no ^)
  5. Block C2: add 0.0.0.0 sfrclak.com to hosts file / firewall 142.11.206.73
<#
.SYNOPSIS
Checks if this machine or project is impacted by the axios npm supply chain compromise (2026-03-31).
.DESCRIPTION
Scans for RAT artifacts, malicious dependencies, and compromised lockfile entries
related to axios@1.14.1 / axios@0.30.4 / plain-crypto-js@4.2.1.
.PARAMETER ProjectDir
Optional path to a Node.js project directory to scan. If omitted, scans common locations.
.LINK
https://github.com/axios/axios/issues/10604
https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan
#>
param(
[string]$ProjectDir
)
$compromised = $false
function Write-Finding {
param([string]$Message)
Write-Host "[!] $Message" -ForegroundColor Red
$script:compromised = $true
}
function Write-Clean {
param([string]$Message)
Write-Host "[OK] $Message" -ForegroundColor Green
}
function Write-Info {
param([string]$Message)
Write-Host "[*] $Message" -ForegroundColor Cyan
}
Write-Host ""
Write-Host "=== axios Supply Chain Compromise Check (2026-03-31) ===" -ForegroundColor Yellow
Write-Host "Checking for: axios@1.14.1, axios@0.30.4, plain-crypto-js@4.2.1"
Write-Host "Reference: https://github.com/axios/axios/issues/10604"
Write-Host ""
# --- 1. Check for Windows RAT artifacts ---
Write-Host "--- 1. Checking for RAT artifacts ---" -ForegroundColor Yellow
$wtExePath = Join-Path $env:ProgramData "wt.exe"
if (Test-Path $wtExePath) {
Write-Finding "RAT artifact found: $wtExePath (PowerShell binary planted by dropper)"
} else {
Write-Clean "No planted wt.exe in ProgramData"
}
$tempVbs = Join-Path $env:TEMP "6202033.vbs"
if (Test-Path $tempVbs) {
Write-Finding "RAT dropper script found: $tempVbs"
} else {
Write-Clean "No dropper VBS in TEMP"
}
$tempPs1 = Join-Path $env:TEMP "6202033.ps1"
if (Test-Path $tempPs1) {
Write-Finding "RAT PowerShell payload found: $tempPs1"
} else {
Write-Clean "No dropper PS1 in TEMP"
}
Write-Host ""
# --- 2. Check DNS / network indicators ---
Write-Host "--- 2. Checking network indicators ---" -ForegroundColor Yellow
$hostsFile = Join-Path $env:SystemRoot "System32\drivers\etc\hosts"
if (Test-Path $hostsFile) {
$hostsContent = Get-Content $hostsFile -Raw
if ($hostsContent -match "sfrclak\.com") {
Write-Info "C2 domain 'sfrclak.com' found in hosts file (may be a block entry ΓÇö verify manually)"
}
}
try {
$dnsResult = Resolve-DnsName -Name "sfrclak.com" -ErrorAction Stop -DnsOnly 2>$null
$c2Ip = $dnsResult | Where-Object { $_.IPAddress -eq "142.11.206.73" }
if ($c2Ip) {
Write-Info "C2 domain sfrclak.com resolves to known malicious IP 142.11.206.73 ΓÇö domain is still active"
} else {
Write-Info "C2 domain sfrclak.com resolves but NOT to known C2 IP (may have been sinkholed)"
}
} catch {
Write-Clean "C2 domain sfrclak.com does not resolve (likely taken down or blocked)"
}
Write-Host ""
# --- 3. Scan project directories ---
Write-Host "--- 3. Scanning for compromised dependencies ---" -ForegroundColor Yellow
$dirsToScan = @()
if ($ProjectDir) {
if (Test-Path $ProjectDir) {
$dirsToScan += $ProjectDir
} else {
Write-Host "Specified project directory not found: $ProjectDir" -ForegroundColor Red
}
} else {
# Scan current directory
$dirsToScan += (Get-Location).Path
Write-Info "No -ProjectDir specified. Scanning current directory: $($dirsToScan[0])"
}
foreach ($dir in $dirsToScan) {
Write-Info "Scanning: $dir"
# Check for plain-crypto-js in node_modules (any depth)
$plainCryptoJsPaths = Get-ChildItem -Path $dir -Filter "plain-crypto-js" -Directory -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "node_modules" }
if ($plainCryptoJsPaths) {
foreach ($p in $plainCryptoJsPaths) {
Write-Finding "Malicious dependency found: $($p.FullName)"
Write-Finding " -> The dropper likely already executed. Treat this system as compromised."
}
} else {
Write-Clean "No plain-crypto-js found in node_modules"
}
# Check package-lock.json files for compromised versions
$lockFiles = Get-ChildItem -Path $dir -Filter "package-lock.json" -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch "node_modules" }
foreach ($lockFile in $lockFiles) {
$content = Get-Content $lockFile.FullName -Raw -ErrorAction SilentlyContinue
if (-not $content) { continue }
# Match the npm registry resolved URL for axios specifically (e.g. "resolved": ".../axios/-/axios-1.14.1.tgz")
# Using the registry path "axios/-/axios-X.Y.Z.tgz" avoids false positives from other packages
# that happen to share the same version number (e.g. dashdash@1.14.1, tslib@1.14.1).
$hasCompromisedAxios = $content -match 'axios/-/axios-1\.14\.1\.tgz' -or
$content -match 'axios/-/axios-0\.30\.4\.tgz'
$hasPlainCryptoJs = $content -match 'plain-crypto-js'
if ($hasCompromisedAxios) {
Write-Finding "Compromised axios version in lockfile: $($lockFile.FullName)"
}
if ($hasPlainCryptoJs) {
Write-Finding "Malicious plain-crypto-js referenced in lockfile: $($lockFile.FullName)"
}
if (-not $hasCompromisedAxios -and -not $hasPlainCryptoJs) {
Write-Clean "Lockfile clean: $($lockFile.FullName)"
}
}
if (-not $lockFiles) {
Write-Info "No package-lock.json files found in $dir (outside node_modules)"
}
}
Write-Host ""
# --- Summary ---
Write-Host "--- Summary ---" -ForegroundColor Yellow
if ($compromised) {
Write-Host ""
Write-Finding "POTENTIAL COMPROMISE DETECTED"
Write-Host ""
Write-Host "Recommended actions:" -ForegroundColor Red
Write-Host " 1. Do NOT clean in place ΓÇö rebuild from a known-good state" -ForegroundColor Red
Write-Host " 2. Rotate ALL credentials (npm tokens, SSH keys, cloud creds, .env files)" -ForegroundColor Red
Write-Host " 3. Audit CI/CD pipelines for affected npm install runs" -ForegroundColor Red
Write-Host " 4. Pin axios to 1.14.0 or 0.30.3" -ForegroundColor Red
Write-Host " 5. Block C2: 0.0.0.0 sfrclak.com in hosts, firewall 142.11.206.73" -ForegroundColor Red
} else {
Write-Host ""
Write-Clean "No indicators of compromise found."
Write-Host ""
Write-Host "Stay safe:" -ForegroundColor Green
Write-Host " - Pin axios to a known-good version (1.14.0 or 1.13.5)" -ForegroundColor Green
Write-Host " - Use 'npm ci' instead of 'npm install'" -ForegroundColor Green
Write-Host " - Consider using --ignore-scripts in CI/CD" -ForegroundColor Green
}
Write-Host ""
#!/usr/bin/env bash
#
# Checks if this machine or project is impacted by the axios npm supply chain
# compromise (2026-03-31).
#
# Usage:
# bash check-axios-compromise.sh [/path/to/project]
#
# Reference:
# https://github.com/axios/axios/issues/10604
# https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
compromised=false
finding() { echo -e "${RED}[!] $1${NC}"; compromised=true; }
clean() { echo -e "${GREEN}[OK] $1${NC}"; }
info() { echo -e "${CYAN}[*] $1${NC}"; }
echo ""
echo -e "${YELLOW}=== axios Supply Chain Compromise Check (2026-03-31) ===${NC}"
echo "Checking for: axios@1.14.1, axios@0.30.4, plain-crypto-js@4.2.1"
echo "Reference: https://github.com/axios/axios/issues/10604"
echo ""
# --- 1. Check for RAT artifacts ---
echo -e "${YELLOW}--- 1. Checking for RAT artifacts ---${NC}"
platform="$(uname -s)"
case "$platform" in
Darwin)
if [ -f "/Library/Caches/com.apple.act.mond" ]; then
finding "RAT artifact found: /Library/Caches/com.apple.act.mond"
else
clean "No macOS RAT artifact (/Library/Caches/com.apple.act.mond)"
fi
;;
Linux)
if [ -f "/tmp/ld.py" ]; then
finding "RAT artifact found: /tmp/ld.py"
else
clean "No Linux RAT artifact (/tmp/ld.py)"
fi
;;
*)
info "Unrecognized platform: $platform ΓÇö check manually for artifacts"
;;
esac
echo ""
# --- 2. Check DNS / network indicators ---
echo -e "${YELLOW}--- 2. Checking network indicators ---${NC}"
if [ -f /etc/hosts ]; then
if grep -q "sfrclak\.com" /etc/hosts 2>/dev/null; then
info "C2 domain 'sfrclak.com' found in /etc/hosts (may be a block entry ΓÇö verify manually)"
fi
fi
if command -v dig &>/dev/null; then
c2_ip=$(dig +short sfrclak.com 2>/dev/null || true)
if [ -z "$c2_ip" ]; then
clean "C2 domain sfrclak.com does not resolve (likely taken down or blocked)"
elif echo "$c2_ip" | grep -q "142.11.206.73"; then
info "C2 domain sfrclak.com resolves to known malicious IP 142.11.206.73 ΓÇö domain is still active"
else
info "C2 domain sfrclak.com resolves to $c2_ip (not the known C2 IP ΓÇö may be sinkholed)"
fi
elif command -v nslookup &>/dev/null; then
if nslookup sfrclak.com &>/dev/null; then
info "C2 domain sfrclak.com still resolves ΓÇö verify if it points to 142.11.206.73"
else
clean "C2 domain sfrclak.com does not resolve"
fi
else
info "No dig or nslookup available ΓÇö cannot check C2 DNS resolution"
fi
echo ""
# --- 3. Scan project directories ---
echo -e "${YELLOW}--- 3. Scanning for compromised dependencies ---${NC}"
scan_dir="${1:-.}"
if [ ! -d "$scan_dir" ]; then
echo -e "${RED}Directory not found: $scan_dir${NC}"
exit 1
fi
info "Scanning: $scan_dir"
# Check for plain-crypto-js in node_modules
plain_crypto_dirs=$(find "$scan_dir" -type d -name "plain-crypto-js" -path "*/node_modules/*" 2>/dev/null || true)
if [ -n "$plain_crypto_dirs" ]; then
while IFS= read -r dir; do
finding "Malicious dependency found: $dir"
finding " -> The dropper likely already executed. Treat this system as compromised."
done <<< "$plain_crypto_dirs"
else
clean "No plain-crypto-js found in node_modules"
fi
# Check package-lock.json files
lock_files=$(find "$scan_dir" -name "package-lock.json" -not -path "*/node_modules/*" 2>/dev/null || true)
if [ -n "$lock_files" ]; then
while IFS= read -r lockfile; do
has_issue=false
# Match the npm registry resolved URL path (e.g. "axios/-/axios-1.14.1.tgz") to avoid
# false positives from unrelated packages sharing the same version number.
if grep -qE 'axios/-/axios-(1\.14\.1|0\.30\.4)\.tgz' "$lockfile" 2>/dev/null; then
finding "Compromised axios version in lockfile: $lockfile"
has_issue=true
fi
if grep -q "plain-crypto-js" "$lockfile" 2>/dev/null; then
finding "Malicious plain-crypto-js referenced in lockfile: $lockfile"
has_issue=true
fi
if [ "$has_issue" = false ]; then
clean "Lockfile clean: $lockfile"
fi
done <<< "$lock_files"
else
info "No package-lock.json files found in $scan_dir (outside node_modules)"
fi
echo ""
# --- Summary ---
echo -e "${YELLOW}--- Summary ---${NC}"
if [ "$compromised" = true ]; then
echo ""
finding "POTENTIAL COMPROMISE DETECTED"
echo ""
echo -e "${RED}Recommended actions:${NC}"
echo -e "${RED} 1. Do NOT clean in place ΓÇö rebuild from a known-good state${NC}"
echo -e "${RED} 2. Rotate ALL credentials (npm tokens, SSH keys, cloud creds, .env files)${NC}"
echo -e "${RED} 3. Audit CI/CD pipelines for affected npm install runs${NC}"
echo -e "${RED} 4. Pin axios to 1.14.0 or 0.30.3${NC}"
echo -e "${RED} 5. Block C2: 0.0.0.0 sfrclak.com in hosts, firewall 142.11.206.73${NC}"
else
echo ""
clean "No indicators of compromise found."
echo ""
echo -e "${GREEN}Stay safe:${NC}"
echo -e "${GREEN} - Pin axios to a known-good version (1.14.0 or 1.13.5)${NC}"
echo -e "${GREEN} - Use 'npm ci' instead of 'npm install'${NC}"
echo -e "${GREEN} - Consider using --ignore-scripts in CI/CD${NC}"
fi
echo ""
@Sagebati

Copy link
Copy Markdown

This condition is false

        if grep -qE '"axios-(1\.14\.1|0\.30\.4)\.tgz"' "$lockfile" 2>/dev/null || \
	           grep -qE '"version":\s*"(1\.14\.1|0\.30\.4)"' "$lockfile" 2>/dev/null; then
	            finding "Compromised axios version in lockfile: $lockfile"
	            has_issue=true
	        fi

the first branch grep -qE '"axios-(1\.14\.1|0\.30\.4)\.tgz"' "$lockfile" 2>/dev/null is correct, but the second one is not, as it just checks that the branch doesn't contain 0.30.4 or 1.14.1 but for any package.

using jq improve the situation

if jq -e '.packages | to_entries | .[] | select(.key | test("node_modules/axios$")) | select(.value.version == "1.14.1" or .value.version == "0.30.4")' "$lockfile" > /dev/null 2>&1; then
	    finding "Compromised axios version in lockfile: $lockfile"
	    has_issue=true
fi

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