Skip to content

Instantly share code, notes, and snippets.

@brysonreece
Last active March 31, 2026 08:56
Show Gist options
  • Select an option

  • Save brysonreece/a248c1203219eeb980da26ceb11f4c9d to your computer and use it in GitHub Desktop.

Select an option

Save brysonreece/a248c1203219eeb980da26ceb11f4c9d to your computer and use it in GitHub Desktop.
Helper scripts to scan your system for compromised Axios versions and plain-crypto-js/RAT artifacts
#!/usr/bin/env pwsh
# axios RAT Scanner — March 31 2026 incident
# Affected: axios@1.14.1, axios@0.30.4 (via plain-crypto-js@4.2.1)
# Ref: https://socket.dev/blog/axios-npm-package-compromised
# Compatible with Windows PowerShell 5.1+ and PowerShell Core 7+
#Requires -Version 5.1
Set-StrictMode -Version Latest
$ErrorActionPreference = 'SilentlyContinue'
# ─── Helpers ─────────────────────────────────────────────────────────────────
function Write-Color {
param([string]$Text, [ConsoleColor]$Color = 'White', [switch]$NoNewline)
$prev = $Host.UI.RawUI.ForegroundColor
$Host.UI.RawUI.ForegroundColor = $Color
if ($NoNewline) { Write-Host $Text -NoNewline } else { Write-Host $Text }
$Host.UI.RawUI.ForegroundColor = $prev
}
function Write-Status {
param([string]$Text)
Write-Host "`r$((' ' * 90))`r" -NoNewline
Write-Color " scanning: $($Text.Substring(0, [Math]::Min(80, $Text.Length)))" -Color DarkGray -NoNewline
}
function Clear-StatusLine {
Write-Host "`r$((' ' * 90))`r" -NoNewline
}
function Write-Rule {
Write-Color ('' * 70) -Color DarkGray
}
# ─── Scan roots ──────────────────────────────────────────────────────────────
# Cover all drives on Windows; fall back to filesystem root on other platforms.
function Get-ScanRoots {
if ($IsWindows -or ($PSVersionTable.PSVersion.Major -le 5)) {
Get-PSDrive -PSProvider FileSystem | Select-Object -ExpandProperty Root
} else {
@('/')
}
}
$foundCount = 0
$pcjsCount = 0
$ratCount = 0
$findings = [System.Collections.Generic.List[string]]::new()
# ─── Phase 1: Poisoned axios versions ────────────────────────────────────────
Write-Host ""
Write-Rule
Write-Color " [1/3] Scanning node_modules for poisoned axios versions..." -Color Cyan
Write-Color " Targets: axios@1.14.1 | axios@0.30.4" -Color DarkGray
Write-Rule
Write-Host ""
$scanRoots = Get-ScanRoots
foreach ($root in $scanRoots) {
Get-ChildItem -Path $root -Recurse -Filter 'package.json' -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\node_modules\\axios\\package\.json$|/node_modules/axios/package\.json$' } |
ForEach-Object {
$pkgPath = $_.FullName
Write-Status $pkgPath
$content = Get-Content $pkgPath -Raw -ErrorAction SilentlyContinue
if ($content -match '"version"\s*:\s*"(1\.14\.1|0\.30\.4)"') {
$ver = $Matches[1]
$foundCount++
Clear-StatusLine
[console]::Beep(880, 300)
Write-Color " !! POISONED axios@$ver found" -Color Red
Write-Color " Path: " -Color Yellow -NoNewline
Write-Host ($pkgPath -replace '[/\\]package\.json$', '')
Write-Host ""
$findings.Add("Poisoned axios@$ver at: $($pkgPath -replace '[/\\]package\.json$','')")
}
}
}
Clear-StatusLine
# ─── Phase 2: plain-crypto-js dropper artifact ───────────────────────────────
Write-Rule
Write-Color " [2/3] Scanning for plain-crypto-js dropper artifact..." -Color Cyan
Write-Color " Folder presence = dropper executed, even if self-deleted" -Color DarkGray
Write-Rule
Write-Host ""
foreach ($root in $scanRoots) {
Get-ChildItem -Path $root -Recurse -Directory -Filter 'plain-crypto-js' -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\node_modules\\plain-crypto-js$|/node_modules/plain-crypto-js$' } |
ForEach-Object {
$dir = $_.FullName
$pcjsCount++
Clear-StatusLine
[console]::Beep(880, 300)
Write-Color " !! DROPPER ARTIFACT: plain-crypto-js found" -Color Red
Write-Color " Path: " -Color Yellow -NoNewline
Write-Host $dir
$pkgJson = Join-Path $dir 'package.json'
if (-not (Test-Path $pkgJson)) {
Write-Color " ⚠ package.json missing — dropper likely already executed" -Color Red
} else {
$pcjsContent = Get-Content $pkgJson -Raw -ErrorAction SilentlyContinue
if ($pcjsContent -match '"version"\s*:\s*"([^"]+)"') {
Write-Color " Version: " -Color Yellow -NoNewline
Write-Host $Matches[1]
}
}
Write-Host ""
$findings.Add("plain-crypto-js artifact at: $dir")
}
}
Clear-StatusLine
# ─── Phase 3: OS-level RAT artifacts ─────────────────────────────────────────
Write-Rule
Write-Color " [3/3] Checking for OS-level RAT artifacts..." -Color Cyan
Write-Rule
Write-Host ""
function Test-RatArtifact {
param([string]$Label, [string]$ArtifactPath)
$resolved = [System.Environment]::ExpandEnvironmentVariables($ArtifactPath)
if (Test-Path $resolved) {
$ratCount++
[console]::Beep(1200, 500)
Write-Color " !! RAT ARTIFACT FOUND ($Label)" -Color Red
Write-Color " Path: " -Color Yellow -NoNewline
Write-Host $resolved
Write-Host ""
$script:findings.Add("RAT artifact ($Label) at: $resolved")
} else {
Write-Color " $Label path not present: $resolved" -Color DarkGray
}
}
$isWin = $IsWindows -or ($PSVersionTable.PSVersion.Major -le 5)
$isMac = $IsMacOS
# Linux also covers WSL — check both Linux and Windows paths under WSL
$isWSL = $IsLinux -and (Test-Path '/proc/sys/fs/binfmt_misc/WSLInterop' -ErrorAction SilentlyContinue)
if ($isWin) {
Test-RatArtifact 'Windows' '%PROGRAMDATA%\wt.exe'
} elseif ($isMac) {
Test-RatArtifact 'macOS' '/Library/Caches/com.apple.act.mond'
} else {
Test-RatArtifact 'Linux' '/tmp/ld.py'
if ($isWSL) {
$winPD = cmd.exe /c 'echo %PROGRAMDATA%' 2>$null
if ($winPD) {
Test-RatArtifact 'Windows (via WSL)' "$($winPD.Trim())\wt.exe"
}
}
}
Write-Host ""
# ─── Summary ─────────────────────────────────────────────────────────────────
Write-Rule
if ($foundCount -gt 0 -or $pcjsCount -gt 0 -or $ratCount -gt 0) {
[console]::Beep(1200, 200); Start-Sleep -Milliseconds 50
[console]::Beep(1200, 200); Start-Sleep -Milliseconds 50
[console]::Beep(1200, 400)
Write-Color " SYSTEM MAY BE COMPROMISED — TAKE IMMEDIATE ACTION" -Color Red
Write-Host ""
Write-Color " Findings:" -Color Yellow
if ($foundCount -gt 0) { Write-Host "$foundCount poisoned axios install(s)" }
if ($pcjsCount -gt 0) { Write-Host "$pcjsCount plain-crypto-js artifact(s)" }
if ($ratCount -gt 0) { Write-Host "$ratCount OS-level RAT artifact(s)" }
Write-Host ""
Write-Color " Required actions:" -Color Yellow
Write-Host " 1. Treat this machine as fully compromised — do not clean in place"
Write-Host " 2. Rotate ALL credentials: npm tokens, SSH keys, AWS/GCP/Azure, .env secrets"
Write-Host " 3. Downgrade axios: npm install axios@1.14.0 (or @0.30.3 for 0.x)"
Write-Host " 4. Remove plain-crypto-js from any node_modules"
Write-Host " 5. Audit CI/CD pipeline runs during the March 31 2026 exposure window"
Write-Host " 6. Block C2 egress — sfrclak.com:8000"
Write-Host ""
Write-Color " Block via Windows Firewall (run as Administrator):" -Color Yellow
Write-Host " New-NetFirewallRule -DisplayName 'Block axios C2' ``"
Write-Host " -Direction Outbound -Action Block ``"
Write-Host " -RemoteAddress 142.11.206.73 -Protocol TCP"
Write-Host ""
Write-Color " Block via hosts file (run as Administrator):" -Color Yellow
Write-Host " Add-Content C:\Windows\System32\drivers\etc\hosts '0.0.0.0 sfrclak.com'"
} else {
Write-Color " Scan complete — no poisoned packages or RAT artifacts found" -Color Green
}
Write-Rule
Write-Host ""
#!/usr/bin/env bash
# axios RAT Scanner — March 31 2026 incident
# Affected: axios@1.14.1, axios@0.30.4 (via plain-crypto-js@4.2.1)
# Ref: https://socket.dev/blog/axios-npm-package-compromised
RED='\033[1;31m'
YEL='\033[1;33m'
GRN='\033[1;32m'
CYN='\033[1;36m'
DIM='\033[2m'
RST='\033[0m'
BELL='\a'
found_count=0
pcjs_count=0
rat_count=0
# ─── Phase 1: Scan for poisoned axios versions ───────────────────────────────
printf "\n${CYN}[1/3] Scanning node_modules for poisoned axios versions...${RST}\n"
printf "${DIM} Targets: axios@1.14.1, axios@0.30.4${RST}\n\n"
while IFS= read -r f; do
v=$(grep -oE '"version"\s*:\s*"(1\.14\.1|0\.30\.4)"' "$f" 2>/dev/null \
| grep -oE '(1\.14\.1|0\.30\.4)')
if [[ -n "$v" ]]; then
(( found_count++ ))
printf "${BELL}${RED} POISONED axios@%s found${RST}\n" "$v"
printf " ${YEL}Path:${RST} %s\n\n" "${f%/package.json}"
else
printf "\r${DIM}scanning: %.80s${RST}\033[K" "$f"
fi
done < <(find / -path '*/node_modules/axios/package.json' -type f 2>/dev/null)
printf "\r\033[K"
# ─── Phase 2: Scan for plain-crypto-js (dropper artifact) ────────────────────
# The RAT self-deletes its own setup.js and package.json after running,
# but the directory itself persists — its presence alone means the dropper ran.
printf "${CYN}[2/3] Scanning for plain-crypto-js dropper artifact...${RST}\n"
printf "${DIM} (folder presence = dropper executed, even if self-deleted)${RST}\n\n"
while IFS= read -r d; do
(( pcjs_count++ ))
printf "${BELL}${RED} DROPPER ARTIFACT: plain-crypto-js found${RST}\n"
printf " ${YEL}Path:${RST} %s\n" "$d"
if [[ ! -f "$d/package.json" ]]; then
printf " ${RED}⚠ package.json missing — dropper likely already executed${RST}\n\n"
else
pcv=$(grep -oE '"version"\s*:\s*"[^"]+"' "$d/package.json" 2>/dev/null \
| grep -oE '"[^"]+"\s*$' | tr -d '"')
printf " ${YEL}Version:${RST} %s\n\n" "$pcv"
fi
done < <(find / -path '*/node_modules/plain-crypto-js' -type d 2>/dev/null)
printf "\r\033[K"
# ─── Phase 3: Check for OS-level RAT artifacts ───────────────────────────────
printf "${CYN}[3/3] Checking for OS-level RAT artifacts...${RST}\n\n"
check_artifact() {
local label="$1" path="$2"
if [[ -e "$path" ]]; then
(( rat_count++ ))
printf "${BELL}${RED} RAT ARTIFACT FOUND (%s)${RST}\n" "$label"
printf " ${YEL}Path:${RST} %s\n\n" "$path"
else
printf " ${DIM}%s path not present: %s${RST}\n" "$label" "$path"
fi
}
case "$(uname -s)" in
Darwin) check_artifact "macOS" "/Library/Caches/com.apple.act.mond" ;;
Linux) check_artifact "Linux" "/tmp/ld.py" ;;
MINGW*|MSYS*|CYGWIN*)
check_artifact "Windows" "${PROGRAMDATA}\\wt.exe" ;;
*)
# WSL: check both Linux and Windows paths
check_artifact "Linux" "/tmp/ld.py"
win_pd=$(cmd.exe /c "echo %PROGRAMDATA%" 2>/dev/null | tr -d '\r')
[[ -n "$win_pd" ]] && check_artifact "Windows (via WSL)" "${win_pd}\\wt.exe"
;;
esac
# ─── Summary ─────────────────────────────────────────────────────────────────
printf "\n${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RST}\n"
if (( found_count > 0 || pcjs_count > 0 || rat_count > 0 )); then
printf "${BELL}${RED} SYSTEM MAY BE COMPROMISED — TAKE IMMEDIATE ACTION${RST}\n\n"
printf "${YEL}Findings:${RST}\n"
(( found_count > 0 )) && printf " • %d poisoned axios install(s)\n" "$found_count"
(( pcjs_count > 0 )) && printf " • %d plain-crypto-js artifact(s)\n" "$pcjs_count"
(( rat_count > 0 )) && printf " • %d OS-level RAT artifact(s)\n" "$rat_count"
printf "\n${YEL}Required actions:${RST}\n"
printf " 1. Treat this machine as fully compromised — do not clean in place\n"
printf " 2. Rotate ALL credentials: npm tokens, SSH keys, AWS/GCP/Azure, .env secrets\n"
printf " 3. Downgrade axios: npm install axios@1.14.0 (or @0.30.3 for 0.x)\n"
printf " 4. Remove plain-crypto-js from any node_modules\n"
printf " 5. Audit CI/CD pipeline runs during the March 31 2026 exposure window\n"
printf " 6. Block C2 egress — sfrclak.com:8000\n"
else
printf "${GRN} scan complete — no poisoned packages or RAT artifacts found${RST}\n"
fi
printf "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RST}\n\n"

Comments are disabled for this gist.