Skip to content

Instantly share code, notes, and snippets.

@FedericoGarcia
Last active June 4, 2026 13:38
Show Gist options
  • Select an option

  • Save FedericoGarcia/803c3ac4e3d93b4042dcd135a43c5a4c to your computer and use it in GitHub Desktop.

Select an option

Save FedericoGarcia/803c3ac4e3d93b4042dcd135a43c5a4c to your computer and use it in GitHub Desktop.
Supply Chain Malware Scanner — PolinRider / InvisibleFerret / Megalodon. Team-wide compromise assessment for JS/Node.js projects.

Supply Chain Malware Scanner

Automated scanner + AI-assisted review guide for known supply chain malware targeting JavaScript/Node.js projects. Designed for team-wide compromise assessment — run the script on every developer machine, then use the AI-assisted checks for deeper analysis.

Read-only — the scripts do not modify files, install packages, or change system state. Safe — credential/token values are never printed in the output. Only existence is reported.

Threats covered

Threat Vector Impact
PolinRider (Lazarus/DPRK) Malicious npm packages with postinstall hooks inject obfuscated code into JS config files Credential theft, SSH keys, env vars, browser data, crypto wallets
InvisibleFerret Second-stage RAT deployed by PolinRider Persistent remote access, full system compromise
Megalodon Compromised GitHub Actions workflows (5,500+ repos, May 2026) CI/CD pipeline injection, secret exfiltration
Nx Console CVE-2026-48027 Compromised VS Code extension v18.95.0 Code execution via trusted extension
axios 1.14.1 / 0.30.4 Compromised maintainer account (March 2026) Backdoored HTTP client library
DPRK npm campaign (108 packages) Typosquatted npm packages (chai-, tailwind-, logging utils) with require-time loaders, blockchain dead-drops Credential theft, SSH key injection, RAT installation, crypto wallet theft

What it checks

Scripted checks (30 checks, run automatically)

Core IOCs (1-4) — direct compromise evidence

  1. Lock file (/tmp/tmp7A863DD1.tmp) — if present, the payload executed
  2. Credential dumps in ~/.npm/ — staging directory for exfiltrated data
  3. Active network connections to known C2 IPs
  4. InvisibleFerret RAT processes

Source code infection (5-10)

  1. Known malware signatures — obfuscated variable names (_$_[hex], _0x[hex]), encoded strings, global namespace injection (global['_V'], globalThis['x'] with short keys)
  2. Suspiciously long lines in config files (payload hides after ~1300 spaces)
  3. Fake .woff2 font files containing JavaScript
  4. Malicious npm packages in package.json
  5. VS Code tasks.json with C2 domain references
  6. Solana/blockchain C2 communication patterns

Supply chain (11-13)

  1. Compromised axios versions (1.14.1, 0.30.4)
  2. Nx Console extension CVE-2026-48027
  3. Suspicious postinstall/preinstall scripts

Propagation (14-15)

  1. .bat files used to rewrite git history silently
  2. Megalodon-style curl|bash patterns in GitHub Actions

Environment integrity (16-20)

  1. Git hooks — global core.hooksPath hijacking
  2. .npmrc auth tokens — existence count (values never printed)
  3. Global npm/pnpm packages inventory
  4. VS Code/Cursor extensions inventory
  5. Persistence mechanisms (LaunchAgents, cron, running processes, VS Code/Cursor payload drops, RAT binaries, obfuscated process detection)

Additional checks (21-23, v1.1)

  1. Embedded scanning tool binaries — committed trufflehog, gitleaks, or detect-secrets binaries that could be trojaned
  2. createRequire(import.meta.url) in application code — CJS bridge that can load native binaries or bypass ESM guards (expected in build tooling, suspicious elsewhere)
  3. SSH key audit — ~/.ssh/authorized_keys modifications, key fingerprints, permission drift, recently modified files

Targeted IOC checks (24-28, v1.2)

  1. Known malicious LaunchAgents — com.bablu.helper.plist (trgrip malware persistence)
  2. Injected SSH keys — checks authorized_keys for known malware keys (bink@DESKTOP-N8JGD6T from js-logger-pack)
  3. Prototype poisoning — Transaction.prototype.moveCall override (crypto-keccak-js stealer), Function.constructor("require") (chai-use-chains dropper)
  4. WASM dropper patterns — Base64-decoded WebAssembly in install scripts (js-logger-pack pattern)
  5. Known malicious npm accounts — references to known attacker accounts in lockfiles

Deep checks (29-30, macOS/Windows)

  • macOS 29: DNS queries to C2 domains (last 7 days via system log)
  • macOS 30: System log entries referencing C2 infrastructure
  • Windows 29: Security Event Log — C2 network events (last 7 days)
  • Windows 30: Security Event Log — suspicious process creation events

AI-assisted checks (require Claude or equivalent)

These checks require semantic understanding and can't be reduced to a shell script. Use them when auditing GitHub activity, reviewing PRs, or investigating suspicious changes.

A1. Diff-level code review

For every commit pushed in the audit window, fetch the full diff and scan for:

Pattern Signal
eval(, new Function(, setTimeout(string) Dynamic code execution
vm.runInNewContext / vm.Script Node.js sandbox escape
child_process.exec / execSync / spawn with dynamic args Command injection
String.fromCharCode with numeric arrays Char-code string assembly
\x?? or \u???? sequences > 10 chars Hex/unicode-escaped payloads
Buffer.from('...', 'base64') outside auth modules Base64 payload decoding
Object.prototype. / __proto__ assignment Prototype pollution
process.env access outside config/env modules Env var harvesting
fs.readFile on ~/.ssh/, ~/.aws/, ~/.config/, ~/.netrc Credential file access
os.homedir() + path joins to dotfiles Home directory traversal
net.Socket / net.connect / dgram in app code Raw socket access
dns.resolve / dns.lookup with non-obvious hostnames DNS exfiltration
Reverse shell patterns: bash -i >& /dev/tcp/, nc -e Reverse shells
Lines > 500 chars in .ts/.js source (not bundles/lockfiles) Minified blob injection
Hardcoded IPs (not 127.0.0.1, 0.0.0.0) Hardcoded C2/exfil endpoints
URLs to non-standard domains in source (not tests/docs) Suspicious external URLs
fetch() / http.request() with dynamic/concatenated URLs Dynamic URL construction
Code that doesn't match the stated commit message Payload hidden in unrelated commit

A2. GitHub Actions workflow review

Scan all .yml and .yaml under .github/workflows/:

Pattern Signal
uses: referencing a mutable tag (@v1, @main, @latest) Tag repoint attack — should be pinned to SHA
${{ github.event.issue.body }} / .title in run: blocks Script injection via PR/issue body
actions/checkout with persist-credentials: true + secrets Token leak risk
curl/wget piped to sh/bash Remote code execution
Secrets used in if: conditions Secret exposure via logs
pull_request_target + checkout of PR head ref Pwn request vulnerability

A3. npm package.json threats (beyond postinstall)

Pattern Signal
New dependencies (not devDependencies) with < 1000 weekly npm downloads Low-popularity supply chain risk
node-gyp / native compilation in install scripts Native code compilation during install
"bin" entries pointing to unexpected files Binary hijacking
Multiple lockfiles (pnpm-lock.yaml + package-lock.json) Lockfile confusion attack

A4. File placement anomalies

Pattern Signal
Executable files (.sh, .py, .rb) outside scripts/, bin/, tooling/ Unexpected executables
Binary files (.so, .dll, .dylib, .wasm, .node) in source Committed native binaries
Dotfiles (.env, .npmrc with auth tokens) committed Committed credentials
New files in node_modules/, .git/hooks/, or build dirs Build artifact injection
keytar / keychain / credential-store access in unexpected code OS credential store theft

A5. Git history integrity

Check How
Force pushes in the audit window gh api repos/{owner}/{repo}/events — look for PushEvent with forced: true
Commits by unexpected authors Compare commit author against known team members
Author ≠ committer mismatch git log --format='%an <%ae> vs %cn <%ce>' — indicates rebase/cherry-pick by different person
Branch protection bypass Check if commits landed on protected branches without PR

Usage

macOS / Linux

chmod +x scan-supply-chain.sh

# Standard scan (runs all checks)
./scan-supply-chain.sh

# Custom source code directories
./scan-supply-chain.sh --scan-dirs "$HOME/work $HOME/personal"

# Or via environment variable
SCAN_DIRS="$HOME/work $HOME/personal" ./scan-supply-chain.sh

Windows (PowerShell)

# Standard scan (runs all checks)
.\Scan-SupplyChain.ps1

# Custom directories
.\Scan-SupplyChain.ps1 -ScanDirs "C:\code", "D:\projects"

Note: Event Log checks on Windows read the Security Event Log, which requires an elevated (Admin) PowerShell session. Non-Admin runs skip those checks gracefully.

With Claude / AI

/security-audit

Or provide this gist as context and ask for a full audit of recent GitHub activity. The AI-assisted checks (A1–A5) require the AI to fetch diffs and apply judgment — they complement but don't replace the automated script.

Default scan directories

The scanner checks these directories if they exist:

macOS/Linux Windows
~/git %USERPROFILE%\git
~/projects %USERPROFILE%\projects
~/dev %USERPROFILE%\dev
~/code %USERPROFILE%\code
~/repos %USERPROFILE%\repos
%USERPROFILE%\source

Override with --scan-dirs (bash) or -ScanDirs (PowerShell).

Reading results

Summary table

 #   Check                               Result
 --- ----------------------------------- ------
   1 Lock file                           ✅
   2 Credential dumps                    ✅
   3 C2 connections                      ✅
   ...

Severity levels

Icon Meaning Action
Clean No action needed
⏭️ Skipped Directory not found or tool not available
👀 Informational Review briefly for anything unexpected
⚠️ Suspicious Needs manual verification — may be a false positive
🔴 Confirmed IOC Stop all work. Do not push. Report immediately.

If you get any 🔴 finding

  1. Do not push to any repository
  2. Do not delete evidence files — they're needed for forensics
  3. Report to your security lead with the full script output
  4. Rotate all secrets: npm tokens, SSH keys, GitHub PATs, API keys, .env values
  5. Check browser saved passwords — InvisibleFerret steals them

If you only get ⚠️ findings

Review each one manually. Common false positives:

  • Blockchain C2 patterns: legitimate if the project uses Solana/Tron
  • Long config lines: some bundled configs legitimately have long lines — check if the length comes from trailing spaces (malware) or actual code
  • postinstall scripts: node scripts/setup.js is normal for many projects — verify the script content
  • .npmrc tokens: expected if you publish to private registries — but rotate them if any 🔴 was found
  • createRequire: normal in build tooling (webpack, vite, tsup configs) — suspicious in application source

Exit codes

  • 0 — no findings (clean)
  • 1 — one or more findings detected

Limitations

This scanner checks for known IOCs from specific campaigns. It does not:

  • Detect novel/zero-day supply chain attacks
  • Monitor runtime behavior or network traffic
  • Replace endpoint detection and response (EDR) tools
  • Scan compiled/minified bundles (only source and config files)
  • Verify git history integrity (use the AI-assisted checks or git log --diff-filter=M -- '*.config.*')

A clean scan is good news but not proof of absence. Keep your EDR active and dependencies audited.

Changelog

v1.2.0

  • Added: 20 C2 IPs (was 1) — consolidated from indece, TrendMicro Void Dokkaebi, and Panther DPRK campaign reports
  • Added: 80+ malicious npm package names (was 6) — covers PolinRider variants, DPRK chai-/tailwind-/logging clusters
  • Added: 39 Vercel C2 domains (was 4) — vscode-, cloudflare, coingecko-liard, chalk-logger, etc.
  • Added: Check #24 — known malicious LaunchAgents (com.bablu.helper.plist from trgrip)
  • Added: Check #25 — injected SSH keys (bink@DESKTOP-N8JGD6T from js-logger-pack)
  • Added: Check #26 — prototype poisoning & dynamic code execution patterns (crypto-keccak-js, chai-use-chains)
  • Added: Check #27 — WASM-based dropper patterns in install scripts (js-logger-pack)
  • Added: Check #28 — known malicious npm account references in lockfiles
  • Improved: Check #20 (persistence) — detects obfuscated malware in running processes, VS Code/Cursor payload drops (f.js, test.js), RAT binary drops (/tmp/0001.dat)
  • Improved: Check #9 (tasks.json) — expanded Vercel C2 domain detection
  • Improved: Check #30 (syslog) — expanded C2 IP and domain predicates
  • Fixed: grep -c || echo "0" double-zero bug in checks #17 (.npmrc) and #29 (DNS) — caused syntax error in expression when no matches found

v1.1.0

  • Fixed: GitHub Actions YAML glob — .yml files were silently skipped due to ungrouped -o in find
  • Improved: Malware signature detection — matches rotating obfuscator patterns (_$_[hex], _0x[hex]) instead of hardcoded stub names
  • Improved: Global namespace injection — catches global['x']= / globalThis['x']= with short keys (stable across PolinRider variants)
  • Added: Check #21 — embedded scanning tool binaries (trufflehog, gitleaks, detect-secrets)
  • Added: Check #22 — createRequire(import.meta.url) in non-tooling application code
  • Added: Check #23 — SSH key audit (~/.ssh permissions, authorized_keys, modification times, fingerprints)
  • Added: AI-assisted checks (A1–A5) — diff review, Actions security, npm threats, file placement, git history integrity
  • Changed: All checks run by default — removed --deep and --ssh flags

v1.0.0

  • Initial release: 20 scripted checks + optional deep scan

References

#!/usr/bin/env bash
# scan-supply-chain.sh — Supply Chain Malware Scanner
# Threats: PolinRider, InvisibleFerret, Megalodon, compromised npm packages
# Read-only: does NOT modify any files or system state
# Safe: never prints credential or token values
#
# Usage: ./scan-supply-chain.sh [--scan-dirs "/path/a /path/b"]
# --scan-dirs Override default source code directories to scan
set -uo pipefail
VERSION="1.2.0"
# ─── Configuration ────────────────────────────────────────────────────────────
DEFAULT_SCAN_DIRS="$HOME"
SCAN_DIRS="${SCAN_DIRS:-$DEFAULT_SCAN_DIRS}"
MAX_DEPTH=6
EXCLUDE_DIRS="node_modules .next dist .git build .turbo .expo .cache Library .Trash .local .nvm .cargo .rustup .docker .colima .orbstack .lima .cache go .gradle .m2 .npm .pnpm-store .yarn .bun Pictures Music Movies Applications"
# Known C2 indicators
C2_IPS="166\.88\.54\.158|198\.105\.127\.210|23\.27\.202\.27|44\.206\.172\.239|195\.201\.194\.107|107\.189\.20\.115|216\.126\.237\.71|216\.126\.224\.220|95\.216\.26\.109|165\.140\.86\.52|66\.235\.175\.48|38\.92\.47\.157|45\.140\.184\.41|136\.0\.9\.8|154\.91\.0\.196|23\.27\.20\.143|85\.239\.62\.36|83\.168\.68\.219|166\.88\.4\.2|23\.27\.120\.142"
C2_DOMAINS="trongrid|aptoslabs|bsc-dataseed|bsc-rpc\.publicnode|api\.telegram\.org|coingecko-liard\.vercel|jsonkeeper\.com|api-sub\.jrodacooker|api\.npoint\.io|jsonspack|nexafilm|deoft|pricesheet|mywalletsss|regioncheck\.xyz"
C2_VERCEL="default-configuration\.vercel|vscode-settings-bootstrap\.vercel|vscode-load-config\.vercel|260120\.vercel|coingecko-liard\.vercel|cloudflareinsights\.vercel|axioshealthcheck\.vercel|logkit-tau\.vercel|wallet-management-tg-bot\.vercel|polymarkettrading\.vercel|npmjs-doc-builder\.vercel|chalk-logger\.vercel|cloudflare-protection\.vercel|cloudflarefirewall\.vercel|cloudflareguard\.vercel|cloudflaresecurity\.vercel|cloudflareshield\.vercel|locate-my-ip\.vercel|vscode-config-settings\.vercel|vscode-extension-260120\.vercel|vscode-settings-config\.vercel|vscode-extensions-bootstrap\.vercel|davhub88\.vercel|codeviewer-three\.vercel|coreviewer\.vercel|vscode-helper171\.vercel|task-hrec\.vercel|vscode-bootstrapper\.vercel|vscode-production-setting\.vercel|vscode-toolkit-settings\.vercel|tailwind-version-4\.vercel|vscode-ext-git\.vercel|thopywork\.vercel|isvalid-regions\.vercel|ext-checkedin\.vercel|data-kappa\.vercel|apiscriptv3\.vercel|server-check-genimi\.vercel|server-genimi-check\.vercel"
# Malicious npm packages (PolinRider + indece + Panther DPRK campaign)
MALICIOUS_PKGS="tailwindcss-style-animate|tailwind-mainanimation|tailwind-tionbased|tailwindcss-typography-style|tailwindcss-style-modify|tailwindcss-animate-style|crypto-keccak-js|simple-auth-basic|swplayer-react-sl|tailwind-typography-cssstyle|tailwindcss-style-typography|tailwind-stylecss-typography|tailwind-typ|tailwindthml-flips|tailwind-lines-clamp|chai-use-chains|trgrip|js-logger-pack|tailwind-animationbasic|tailwind-configuration|tailwind-scroller|tailwind-text-fill|tailwindcss-fonttype-inter|tailwindcss-typeface-inter|chai-as-adapter|chai-as-chain-v2|chai-as-char|chai-as-elevated|chai-as-encrypted|chai-as-evm|chai-as-hooked|chai-as-ide|chai-as-init|chai-as-inserted|chai-as-mobj|chai-as-nobj|chai-as-optimized|chai-as-refined|chai-as-type|chai-beta|chai-chain-coremesh|chai-extensions-extras|chalk-ts-logger|cli-pretty-logger|color-cli-log|dev-log-core|jonas-prettier-logger|log-upgrade|pino-pretty-logs|prettier-logger|terminal-structured-logger|express-flowlimit|winston-prism|vite-enhancer-config|request-js-validator|mongoose-lean-hooks|polymarket-onchain-sdk|mexc-utils-helper|sol-sdk|ts-relayer-client|relion-chain|metrify-chain|trackora-chain|bundrix|argonflux|byteboxlab|byteutilsbox|cookie-parseflow|coremesh|express-auth-basic|express-lib-validator|express-session-validator|json-spectaculation|jsontoken-extend|levex-press|peptide-score|peptideenv|rollup-plugin-polyfill-route|vime-azl|wime-zle|atddevs|farm-orchestrator|farm-runner|df-vision|appclaw"
# Known malicious npm accounts (indece + Panther reports)
MALICIOUS_NPM_ACCOUNTS="msafe-ci|cubistengineer|ficara9076|calebguo|wakiye8356|codeforge1144|lancer110|lancer117|tailwind11995|mwai2006|joshsingh|jpeek868|jsonspack|corvettdan1963|chainlence|vynlence|koldavich|ronaldobon"
# Known malicious SSH keys (js-logger-pack)
MALICIOUS_SSH_KEY="bink@DESKTOP-N8JGD6T"
# Known malicious LaunchAgents (trgrip)
MALICIOUS_LAUNCHAGENTS="com.bablu.helper.plist"
# Known safe postinstall commands (prefix match)
SAFE_POSTINSTALL="prisma generate|patch-package|husky|ngcc|electron-rebuild|node-gyp|esbuild|tsc |tsx |ts-node|npx prisma|playwright install|puppeteer install|opencollective|is-ci|npm run build|pnpm run build|yarn build|expo-configure-project|pod-install"
# ─── Output helpers ───────────────────────────────────────────────────────────
if [[ -t 1 ]]; then
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
else
RED=''; GREEN=''; YELLOW=''; CYAN=''; BOLD=''; DIM=''; NC=''
fi
TOTAL_CHECKS=28
if [[ "$(uname)" == "Darwin" ]]; then TOTAL_CHECKS=30; fi
CURRENT=0
declare -a CHECK_NAMES=()
declare -a CHECK_RESULTS=()
declare -a CHECK_DETAILS=()
FINDINGS=0
header() {
CURRENT=$((CURRENT + 1))
printf "\n${CYAN}[%2d/%d]${NC} ${BOLD}%s${NC}\n" "$CURRENT" "$TOTAL_CHECKS" "$1"
}
pass() { printf " ${GREEN}✅ %s${NC}\n" "$1"; }
warn() { printf " ${YELLOW}⚠️ %s${NC}\n" "$1"; FINDINGS=$((FINDINGS + 1)); }
fail() { printf " ${RED}🔴 %s${NC}\n" "$1"; FINDINGS=$((FINDINGS + 1)); }
detail() { printf " ${DIM} %s${NC}\n" "$1"; }
record() {
CHECK_NAMES+=("$1")
CHECK_RESULTS+=("$2")
}
# ─── Resolve scan directories ────────────────────────────────────────────────
RESOLVED_DIRS=()
resolve_dirs() {
for dir in $SCAN_DIRS; do
if [[ -d "$dir" ]]; then
RESOLVED_DIRS+=("$dir")
fi
done
if [[ ${#RESOLVED_DIRS[@]} -eq 0 ]]; then
printf "${YELLOW}Warning: no scan directories found. Checked: %s${NC}\n" "$SCAN_DIRS"
printf "Override with: SCAN_DIRS=\"/your/code\" ./scan-supply-chain.sh\n"
fi
}
# Build find exclude arguments: -path '*/node_modules' -prune -o ...
build_find_excludes() {
local excludes=""
for d in $EXCLUDE_DIRS; do
excludes="$excludes -path '*/$d' -prune -o"
done
echo "$excludes"
}
# Build grep exclude-dir arguments
build_grep_excludes() {
local excludes=""
for d in $EXCLUDE_DIRS; do
excludes="$excludes --exclude-dir=$d"
done
echo "$excludes"
}
GREP_EXCLUDES=$(build_grep_excludes)
# ─── Check functions ─────────────────────────────────────────────────────────
# 1. Lock file — direct evidence of payload execution
check_lock_file() {
header "Lock file (payload execution marker)"
local lockfile="/tmp/tmp7A863DD1.tmp"
if [[ -f "$lockfile" ]]; then
fail "LOCK FILE EXISTS at $lockfile — payload executed on this machine"
detail "$(ls -la "$lockfile")"
record "Lock file" "🔴"
else
pass "Not found"
record "Lock file" "✅"
fi
}
# 2. Credential dumps in ~/.npm staging directory
check_credential_dumps() {
header "Credential dumps (~/.npm staging)"
local found=0
local files
files=$(find "$HOME/.npm" \( \
-name "_credentials.json" -o \
-name "_sysenv.json" -o \
-name "_sysenv.env" -o \
-name "_info.json" -o \
-name "*.zip" \
\) 2>/dev/null || true)
if [[ -n "$files" ]]; then
found=$(echo "$files" | wc -l | tr -d ' ')
fail "CREDENTIAL DUMPS FOUND — $found file(s) in ~/.npm"
echo "$files" | while IFS= read -r f; do
detail "$(ls -la "$f" 2>/dev/null)"
done
detail "DO NOT delete these — they are forensic evidence"
record "Credential dumps" "🔴"
else
pass "No suspicious files"
record "Credential dumps" "✅"
fi
}
# 3. Active network connections to known C2
check_network_c2() {
header "Active C2 network connections"
local hits
hits=$(netstat -an 2>/dev/null | grep -iE "$C2_IPS" || true)
if [[ -n "$hits" ]]; then
fail "ACTIVE CONNECTION TO C2 SERVER"
echo "$hits" | while IFS= read -r line; do detail "$line"; done
record "C2 connections" "🔴"
else
pass "No active connections to known C2 IPs"
record "C2 connections" "✅"
fi
}
# 4. InvisibleFerret active processes
check_invisibleferret() {
header "InvisibleFerret RAT processes"
local hits
hits=$(ps aux 2>/dev/null | grep -iE "__DAEMONIZED|cat\.py" | grep -v grep || true)
if [[ -n "$hits" ]]; then
fail "INVISIBLEFERRET PROCESS DETECTED"
echo "$hits" | while IFS= read -r line; do detail "$line"; done
record "InvisibleFerret" "🔴"
else
pass "No InvisibleFerret processes found"
record "InvisibleFerret" "✅"
fi
}
# 5. Known malware signatures in JS files
check_malware_signatures() {
header "Known malware signatures in source files"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Malware signatures" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts' \
-E "rmcej%otb%|Cot%3t=shtP|_\\\$_[a-f0-9]{4}|_0x[a-f0-9]{4}|global\['\w{1,3}'\]\s*=|globalThis\['\w{1,3}'\]\s*=" \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
local count
count=$(echo "$hits" | wc -l | tr -d ' ')
fail "MALWARE SIGNATURES FOUND in $count file(s)"
echo "$hits" | head -20 | while IFS= read -r f; do detail "$f"; done
[[ $count -gt 20 ]] && detail "... and $((count - 20)) more"
record "Malware signatures" "🔴"
else
pass "No known signatures found"
record "Malware signatures" "✅"
fi
}
# 6. Suspiciously long lines in config files (payload hides after ~1300 spaces)
check_long_config_lines() {
header "Suspiciously long config lines (>200 chars)"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Long config lines" "⏭️"; return; }
local config_names="-name 'postcss.config.*' -o -name 'next.config.*' -o -name 'tailwind.config.*' \
-o -name 'eslint.config.*' -o -name 'babel.config.*' -o -name 'jest.config.*' \
-o -name 'vite.config.*' -o -name 'tsup.config.*' -o -name 'astro.config.*' \
-o -name 'webpack.config.*' -o -name 'vue.config.*' -o -name 'App.js'"
local found=0
local output=""
for dir in "${RESOLVED_DIRS[@]}"; do
while IFS= read -r file; do
[[ -z "$file" ]] && continue
local longest
longest=$(awk '{ print length }' "$file" 2>/dev/null | sort -rn | head -1)
if [[ -n "$longest" ]] && [[ "$longest" -gt 200 ]] 2>/dev/null; then
output+="⚠️ SUSPICIOUS ($longest chars): $file"$'\n'
found=$((found + 1))
fi
done < <(find "$dir" -maxdepth "$MAX_DEPTH" \
-path '*/node_modules' -prune -o \
-path '*/.next' -prune -o \
-path '*/dist' -prune -o \
-path '*/.git' -prune -o \
\( -name 'postcss.config.*' -o -name 'next.config.*' -o -name 'tailwind.config.*' \
-o -name 'eslint.config.*' -o -name 'babel.config.*' -o -name 'jest.config.*' \
-o -name 'vite.config.*' -o -name 'tsup.config.*' -o -name 'astro.config.*' \
-o -name 'webpack.config.*' -o -name 'vue.config.*' -o -name 'App.js' \) \
-print 2>/dev/null)
done
if [[ $found -gt 0 ]]; then
warn "FOUND $found config file(s) with suspiciously long lines"
echo "$output" | while IFS= read -r line; do [[ -n "$line" ]] && detail "$line"; done
detail "Inspect these files — the payload hides after ~1300 trailing spaces"
record "Long config lines" "⚠️"
else
pass "All config files have normal line lengths"
record "Long config lines" "✅"
fi
}
# 7. Fake font files (.woff2 containing JS)
check_fake_fonts() {
header "Fake font payloads (.woff2 with JS)"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Fake fonts" "⏭️"; return; }
local found=0
for dir in "${RESOLVED_DIRS[@]}"; do
while IFS= read -r file; do
[[ -z "$file" ]] && continue
if head -c 100 "$file" 2>/dev/null | grep -qE "global|function|require|module\.exports"; then
warn "JS payload in font: $file"
found=$((found + 1))
fi
done < <(find "$dir" -maxdepth "$MAX_DEPTH" \
-path '*/node_modules' -prune -o \
-path '*/.next' -prune -o \
-name '*.woff2' -print 2>/dev/null)
done
if [[ $found -gt 0 ]]; then
record "Fake fonts" "⚠️"
else
pass "No fake font payloads found"
record "Fake fonts" "✅"
fi
}
# 8. Malicious npm packages in package.json
check_malicious_packages() {
header "Malicious npm packages in package.json"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Malicious packages" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='package.json' \
-E "$MALICIOUS_PKGS" \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
local count
count=$(echo "$hits" | wc -l | tr -d ' ')
fail "MALICIOUS PACKAGES in $count package.json file(s)"
echo "$hits" | while IFS= read -r f; do detail "$f"; done
record "Malicious packages" "🔴"
else
pass "No known malicious packages found"
record "Malicious packages" "✅"
fi
}
# 9. VS Code tasks.json with C2 domains
check_vscode_tasks() {
header "VS Code tasks.json with C2 domains"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "tasks.json C2" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='tasks.json' \
-E "$C2_VERCEL|e9b53a7c-2342-4b15-b02d-bd8b8f6a03f9" \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
fail "MALICIOUS tasks.json FOUND"
echo "$hits" | while IFS= read -r f; do detail "$f"; done
record "tasks.json C2" "🔴"
else
pass "No malicious tasks.json files found"
record "tasks.json C2" "✅"
fi
}
# 10. Solana/blockchain C2 patterns in source code
check_solana_c2() {
header "Solana/blockchain C2 patterns in source"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Blockchain C2" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts' \
-E "@solana/web3|trongrid|wallet.*memo" \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
local count
count=$(echo "$hits" | wc -l | tr -d ' ')
warn "BLOCKCHAIN REFERENCES in $count file(s) — verify if legitimate"
echo "$hits" | head -10 | while IFS= read -r f; do detail "$f"; done
detail "May be legitimate if this project uses Solana/Tron — verify manually"
record "Blockchain C2" "⚠️"
else
pass "No blockchain C2 patterns found"
record "Blockchain C2" "✅"
fi
}
# 11. Compromised axios versions (1.14.1, 0.30.4)
check_axios_compromised() {
header "Compromised axios versions"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Axios versions" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='package.json' --include='pnpm-lock.yaml' \
--include='package-lock.json' --include='yarn.lock' \
-E '"axios":\s*"(1\.14\.1|0\.30\.4)"|axios@(1\.14\.1|0\.30\.4)' \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
fail "COMPROMISED AXIOS VERSION found"
echo "$hits" | while IFS= read -r f; do detail "$f"; done
detail "axios 1.14.1 and 0.30.4 were published by a compromised maintainer"
record "Axios versions" "🔴"
else
pass "No compromised axios versions found"
record "Axios versions" "✅"
fi
}
# 12. Nx Console CVE-2026-48027
check_nx_console() {
header "Nx Console extension (CVE-2026-48027)"
local exts=""
exts=$(code --list-extensions --show-versions 2>/dev/null || \
cursor --list-extensions --show-versions 2>/dev/null || true)
if echo "$exts" | grep -iqE "nrwl\.angular-console|nx-console"; then
local version
version=$(echo "$exts" | grep -iE "nrwl|nx-console" | head -1)
if echo "$version" | grep -q "18\.95\.0"; then
fail "COMPROMISED Nx Console v18.95.0 installed — update to 18.100.0+"
detail "$version"
record "Nx Console" "🔴"
else
pass "Nx Console installed but version is safe: $version"
record "Nx Console" "✅"
fi
else
pass "Nx Console not installed"
record "Nx Console" "✅"
fi
}
# 13. Suspicious postinstall/preinstall scripts
check_postinstall_scripts() {
header "Suspicious postinstall/preinstall scripts"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "postinstall" "⏭️"; return; }
local found=0
for dir in "${RESOLVED_DIRS[@]}"; do
while IFS= read -r pkg; do
[[ -z "$pkg" ]] && continue
local scripts
scripts=$(python3 -c "
import json, sys
try:
d = json.load(open('$pkg'))
s = d.get('scripts', {})
for k in ('postinstall', 'preinstall'):
if k in s:
print(f'{k}: {s[k]}')
except: pass
" 2>/dev/null || true)
if [[ -n "$scripts" ]]; then
while IFS= read -r script_line; do
local cmd="${script_line#*: }"
local is_safe=false
IFS='|' read -ra safe_patterns <<< "$SAFE_POSTINSTALL"
for pattern in "${safe_patterns[@]}"; do
if [[ "$cmd" == *"$pattern"* ]]; then
is_safe=true
break
fi
done
if ! $is_safe; then
warn "REVIEW: $pkg"
detail "$script_line"
found=$((found + 1))
fi
done <<< "$scripts"
fi
done < <(find "$dir" -maxdepth 4 \
-path '*/node_modules' -prune -o \
-name 'package.json' -print 2>/dev/null)
done
if [[ $found -gt 0 ]]; then
detail "Review these scripts — not all are malicious, but verify unknown commands"
record "postinstall" "⚠️"
else
pass "No suspicious install scripts found"
record "postinstall" "✅"
fi
}
# 14. Propagation artifacts (.bat files)
check_propagation_artifacts() {
header "Propagation artifacts (.bat files, .gitignore entries)"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Propagation" "⏭️"; return; }
local found=0
for dir in "${RESOLVED_DIRS[@]}"; do
local bats
bats=$(find "$dir" -maxdepth "$MAX_DEPTH" \
-path '*/node_modules' -prune -o \
\( -name 'temp_auto_push.bat' -o -name 'config.bat' \) \
-print 2>/dev/null || true)
if [[ -n "$bats" ]]; then
echo "$bats" | while IFS= read -r f; do
warn "Propagation artifact: $f"
found=$((found + 1))
done
fi
done
local gitignore_refs
gitignore_refs=$(grep -rl $GREP_EXCLUDES \
--include='.gitignore' \
'config\.bat' \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$gitignore_refs" ]]; then
echo "$gitignore_refs" | while IFS= read -r f; do
warn "config.bat in .gitignore: $f"
found=$((found + 1))
done
fi
if [[ $found -eq 0 ]]; then
pass "No propagation artifacts found"
record "Propagation" "✅"
else
record "Propagation" "⚠️"
fi
}
# 15. Megalodon GitHub Actions injection
check_megalodon_actions() {
header "Megalodon GitHub Actions injection"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "GH Actions" "⏭️"; return; }
local found=0
for dir in "${RESOLVED_DIRS[@]}"; do
while IFS= read -r workflow; do
[[ -z "$workflow" ]] && continue
if grep -qE 'curl\s.*\|\s*(sh|bash)|wget\s.*\|\s*(sh|bash)|base64\s+(-d|--decode)\s*\||\beval\s+\$\(' "$workflow" 2>/dev/null; then
warn "Suspicious pattern in: $workflow"
found=$((found + 1))
fi
done < <(find "$dir" -maxdepth "$MAX_DEPTH" \
-path '*/.github/workflows' -type d -exec find {} \( -name '*.yml' -o -name '*.yaml' \) \; 2>/dev/null)
done
if [[ $found -gt 0 ]]; then
detail "Review these workflows — curl|bash and base64 decode in Actions are red flags"
record "GH Actions" "⚠️"
else
pass "No suspicious GitHub Actions patterns found"
record "GH Actions" "✅"
fi
}
# 16. Git hooks — hijacked global hooks
check_git_hooks() {
header "Git hooks integrity"
local found=0
local hooks_path
hooks_path=$(git config --global core.hooksPath 2>/dev/null || true)
if [[ -n "$hooks_path" ]]; then
warn "Global hooks path is set: $hooks_path"
if [[ -d "$hooks_path" ]]; then
detail "Contents: $(ls "$hooks_path" 2>/dev/null || echo '(empty)')"
fi
found=$((found + 1))
else
pass "No global hooksPath override"
fi
for hook_dir in "$HOME/.config/git/hooks" "$HOME/.git-templates/hooks"; do
if [[ -d "$hook_dir" ]] && [[ -n "$(ls -A "$hook_dir" 2>/dev/null)" ]]; then
warn "Hook templates found in: $hook_dir"
detail "$(ls "$hook_dir")"
found=$((found + 1))
fi
done
if [[ $found -eq 0 ]]; then
record "Git hooks" "✅"
else
detail "Verify these hooks are legitimate — malware uses global hooks for persistence"
record "Git hooks" "⚠️"
fi
}
# 17. npm auth tokens (.npmrc) — existence check only, NEVER print values
check_npmrc_tokens() {
header "npm auth tokens (.npmrc)"
local npmrc="$HOME/.npmrc"
if [[ -f "$npmrc" ]]; then
local token_lines
token_lines=$(grep -E '(authToken|_auth|_password)' "$npmrc" 2>/dev/null | wc -l | tr -d ' ')
if [[ "$token_lines" -gt 0 ]]; then
warn ".npmrc contains $token_lines line(s) with auth tokens"
detail "If compromised, rotate these tokens immediately"
detail "Token values intentionally hidden — inspect manually: cat ~/.npmrc"
record ".npmrc tokens" "⚠️"
else
pass ".npmrc exists but contains no auth tokens"
record ".npmrc tokens" "✅"
fi
else
pass "No .npmrc file found"
record ".npmrc tokens" "✅"
fi
}
# 18. Global packages inventory
check_global_packages() {
header "Global packages (npm/pnpm)"
# Resolve the real npm/pnpm binaries to avoid shell lazy-loaders (nvm/oh-my-zsh)
local npm_bin pnpm_bin
npm_bin=$(command -v npm 2>/dev/null || true)
# If npm is a shell function (lazy-loader), try the nvm default path
if [[ -z "$npm_bin" ]] || type npm 2>/dev/null | head -1 | grep -q "function"; then
local nvm_npm="${NVM_DIR:-$HOME/.nvm}/versions/node/$(ls "${NVM_DIR:-$HOME/.nvm}/versions/node/" 2>/dev/null | sort -V | tail -1)/bin/npm"
[[ -x "$nvm_npm" ]] && npm_bin="$nvm_npm"
fi
if [[ -n "$npm_bin" && -x "$npm_bin" ]]; then
printf " ${DIM}npm global:${NC}\n"
"$npm_bin" ls -g --depth=0 2>/dev/null | tail -n +2 | while IFS= read -r line; do detail "$line"; done
else
detail "npm not found — skipped"
fi
pnpm_bin=$(which pnpm 2>/dev/null || true)
if [[ -z "$pnpm_bin" ]]; then
# Try common corepack/nvm paths
local nvm_pnpm="${NVM_DIR:-$HOME/.nvm}/versions/node/$(ls "${NVM_DIR:-$HOME/.nvm}/versions/node/" 2>/dev/null | sort -V | tail -1)/bin/pnpm"
[[ -x "$nvm_pnpm" ]] && pnpm_bin="$nvm_pnpm"
fi
if [[ -n "$pnpm_bin" && -x "$pnpm_bin" ]]; then
printf " ${DIM}pnpm global:${NC}\n"
"$pnpm_bin" ls -g 2>/dev/null | tail -n +2 | while IFS= read -r line; do detail "$line"; done
else
detail "pnpm not found — skipped"
fi
pass "Review the lists above for any unfamiliar packages"
record "Global packages" "👀"
}
# 19. VS Code / Cursor extensions
check_extensions() {
header "Editor extensions"
local exts=""
if command -v code &>/dev/null; then
printf " ${DIM}VS Code extensions:${NC}\n"
exts=$(code --list-extensions 2>/dev/null || true)
elif command -v cursor &>/dev/null; then
printf " ${DIM}Cursor extensions:${NC}\n"
exts=$(cursor --list-extensions 2>/dev/null || true)
fi
if [[ -n "$exts" ]]; then
echo "$exts" | while IFS= read -r ext; do detail "$ext"; done
pass "Review for unfamiliar extensions"
else
pass "No editor extensions found (or editor CLI not available)"
fi
record "Extensions" "👀"
}
# 20. Persistence mechanisms
check_persistence() {
header "Persistence mechanisms"
local found=0
if [[ "$(uname)" == "Darwin" ]]; then
local agents_dir="$HOME/Library/LaunchAgents"
if [[ -d "$agents_dir" ]] && [[ -n "$(ls -A "$agents_dir" 2>/dev/null)" ]]; then
printf " ${DIM}LaunchAgents:${NC}\n"
ls "$agents_dir" 2>/dev/null | while IFS= read -r f; do detail "$f"; done
else
detail "LaunchAgents: (empty)"
fi
fi
local cron
cron=$(crontab -l 2>/dev/null || true)
if [[ -n "$cron" ]]; then
printf " ${DIM}Crontab:${NC}\n"
echo "$cron" | while IFS= read -r line; do detail "$line"; done
else
detail "Crontab: (empty)"
fi
printf " ${DIM}Suspicious processes (node/npm/python, excluding IDE):${NC}\n"
local procs
procs=$(ps aux 2>/dev/null | grep -iE 'node|npm|npx|python' | \
grep -viE 'grep|Claude|cursor|vscode|Code Helper|Electron|copilot|eslint_d' || true)
if [[ -n "$procs" ]]; then
echo "$procs" | while IFS= read -r line; do detail "$line"; done
# Check for known malware patterns in process list
if echo "$procs" | grep -qE "_\\\$_[a-f0-9]{4}|_0x[a-f0-9]{4}|global\['|node -e.*global|__DAEMONIZED" 2>/dev/null; then
fail "MALWARE PROCESS DETECTED — obfuscated code in running process"
found=$((found + 1))
fi
else
detail "(none)"
fi
# VS Code / Cursor payload drops (BeaverTail staging)
for payload_dir in "$HOME/.vscode" "$HOME/.cursor" "$HOME/.windsurf" "$HOME/.pearai"; do
for payload in "f.js" "test.js"; do
if [[ -f "$payload_dir/$payload" ]]; then
warn "Payload drop found: $payload_dir/$payload"
detail "$(ls -la "$payload_dir/$payload" 2>/dev/null)"
found=$((found + 1))
fi
done
done
# RAT binary drop
if [[ -f "/tmp/0001.dat" ]]; then
fail "RAT BINARY DROP: /tmp/0001.dat"
detail "$(ls -la /tmp/0001.dat 2>/dev/null)"
found=$((found + 1))
fi
if [[ $found -gt 0 ]]; then
record "Persistence" "🔴"
else
pass "Review items above for anything unexpected"
record "Persistence" "👀"
fi
}
# 21. Embedded scanning tool binaries (trojaned trufflehog, etc.)
check_embedded_tools() {
header "Embedded scanning tool binaries"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Embedded tools" "⏭️"; return; }
local found=0
for dir in "${RESOLVED_DIRS[@]}"; do
local hits
hits=$(find "$dir" -maxdepth "$MAX_DEPTH" \
-path '*/node_modules' -prune -o \
-path '*/.git' -prune -o \
\( -name 'trufflehog*' -o -name 'gitleaks*' -o -name 'detect-secrets*' \) \
-type f -print 2>/dev/null || true)
if [[ -n "$hits" ]]; then
echo "$hits" | while IFS= read -r f; do
warn "Embedded scanning tool: $f"
detail "$(ls -la "$f" 2>/dev/null)"
found=$((found + 1))
done
fi
done
if [[ $found -eq 0 ]]; then
pass "No embedded scanning tool binaries"
record "Embedded tools" "✅"
else
detail "Committed scanning binaries could be trojaned — verify provenance"
record "Embedded tools" "⚠️"
fi
}
# 22. createRequire(import.meta.url) in application code
check_create_require() {
header "createRequire(import.meta) in application code"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "createRequire" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='*.js' --include='*.mjs' --include='*.ts' \
-E "createRequire\(import\.meta" \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
local suspicious=0
echo "$hits" | while IFS= read -r f; do
if echo "$f" | grep -qiE '\.config\.|scripts/|bin/|cli|build|webpack|rollup|vite|esbuild|tsup'; then
detail "Expected (tooling): $f"
else
warn "Suspicious createRequire: $f"
suspicious=$((suspicious + 1))
fi
done
if [[ $suspicious -eq 0 ]]; then
pass "createRequire found only in expected tooling files"
record "createRequire" "✅"
else
detail "createRequire can load native binaries or bypass ESM — verify usage"
record "createRequire" "⚠️"
fi
else
pass "No createRequire(import.meta) found"
record "createRequire" "✅"
fi
}
# 23. SSH key audit (opt-in: --ssh)
check_ssh_keys() {
header "SSH key audit (~/.ssh)"
local ssh_dir="$HOME/.ssh"
if [[ ! -d "$ssh_dir" ]]; then
pass "~/.ssh does not exist"
record "SSH keys" "✅"
return
fi
local found=0
# Permission checks
local dir_perms
dir_perms=$(stat -f '%Lp' "$ssh_dir" 2>/dev/null || stat -c '%a' "$ssh_dir" 2>/dev/null || echo "unknown")
if [[ "$dir_perms" != "700" ]] && [[ "$dir_perms" != "unknown" ]]; then
warn "~/.ssh permissions are $dir_perms (expected 700)"
found=$((found + 1))
else
pass "~/.ssh permissions: $dir_perms"
fi
if [[ -f "$ssh_dir/authorized_keys" ]]; then
local ak_perms
ak_perms=$(stat -f '%Lp' "$ssh_dir/authorized_keys" 2>/dev/null || stat -c '%a' "$ssh_dir/authorized_keys" 2>/dev/null || echo "unknown")
if [[ "$ak_perms" != "600" ]] && [[ "$ak_perms" != "unknown" ]]; then
warn "authorized_keys permissions are $ak_perms (expected 600)"
found=$((found + 1))
fi
local key_count
key_count=$(wc -l < "$ssh_dir/authorized_keys" 2>/dev/null | tr -d ' ')
detail "authorized_keys: $key_count key(s)"
# Fingerprints (NOT private keys)
ssh-keygen -lf "$ssh_dir/authorized_keys" 2>/dev/null | while IFS= read -r line; do
detail " $line"
done
fi
# Modification times
detail "File modification times:"
local recent_threshold
recent_threshold=$(date -v-24H '+%s' 2>/dev/null || date -d '24 hours ago' '+%s' 2>/dev/null || echo "0")
while IFS= read -r file; do
[[ -z "$file" ]] && continue
local mtime
mtime=$(stat -f '%m' "$file" 2>/dev/null || stat -c '%Y' "$file" 2>/dev/null || echo "0")
local mtime_human
mtime_human=$(stat -f '%Sm' "$file" 2>/dev/null || stat -c '%y' "$file" 2>/dev/null || echo "unknown")
detail " $mtime_human $(basename "$file")"
if [[ "$mtime" -gt "$recent_threshold" ]] 2>/dev/null; then
warn "RECENTLY MODIFIED (last 24h): $file"
found=$((found + 1))
fi
done < <(find "$ssh_dir" -maxdepth 1 -type f 2>/dev/null)
if [[ $found -eq 0 ]]; then
pass "SSH key state looks normal"
record "SSH keys" "✅"
else
record "SSH keys" "⚠️"
fi
}
# 24. Known malicious LaunchAgents (trgrip, js-logger-pack)
check_malicious_launchagents() {
header "Known malicious LaunchAgents"
if [[ "$(uname)" != "Darwin" ]]; then
pass "macOS only — skipped"
record "Malicious LaunchAgents" "⏭️"
return
fi
local agents_dir="$HOME/Library/LaunchAgents"
local found=0
if [[ -d "$agents_dir" ]]; then
IFS='|' read -ra agents <<< "$MALICIOUS_LAUNCHAGENTS"
for agent in "${agents[@]}"; do
if [[ -f "$agents_dir/$agent" ]]; then
fail "MALICIOUS LAUNCHAGENT: $agents_dir/$agent"
detail "$(ls -la "$agents_dir/$agent" 2>/dev/null)"
detail "$(cat "$agents_dir/$agent" 2>/dev/null | head -20)"
found=$((found + 1))
fi
done
fi
if [[ $found -eq 0 ]]; then
pass "No known malicious LaunchAgents"
record "Malicious LaunchAgents" "✅"
else
record "Malicious LaunchAgents" "🔴"
fi
}
# 25. Injected SSH keys (js-logger-pack)
check_injected_ssh_keys() {
header "Injected SSH keys (known malware keys)"
local ak="$HOME/.ssh/authorized_keys"
if [[ ! -f "$ak" ]]; then
pass "No authorized_keys file"
record "Injected SSH keys" "✅"
return
fi
local hits
hits=$(grep -c "$MALICIOUS_SSH_KEY" "$ak" 2>/dev/null || echo "0")
if [[ "$hits" -gt 0 ]]; then
fail "MALICIOUS SSH KEY FOUND in authorized_keys ($MALICIOUS_SSH_KEY)"
detail "This key was injected by js-logger-pack malware"
detail "Remove it immediately: grep -v '$MALICIOUS_SSH_KEY' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp && mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys"
record "Injected SSH keys" "🔴"
else
pass "No known malicious SSH keys"
record "Injected SSH keys" "✅"
fi
}
# 26. Prototype poisoning / dynamic code execution patterns
check_prototype_poisoning() {
header "Prototype poisoning & dynamic code execution"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Prototype poisoning" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts' \
-E "Transaction\.prototype\.moveCall|Function\.constructor\(\"require\"|Function\.constructor\('require'" \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
local count
count=$(echo "$hits" | wc -l | tr -d ' ')
fail "PROTOTYPE POISONING PATTERNS in $count file(s)"
echo "$hits" | head -10 | while IFS= read -r f; do detail "$f"; done
detail "Transaction.prototype override = crypto stealer (crypto-keccak-js pattern)"
detail "Function.constructor('require') = dynamic C2 dropper (chai-use-chains pattern)"
record "Prototype poisoning" "🔴"
else
pass "No prototype poisoning patterns found"
record "Prototype poisoning" "✅"
fi
}
# 27. WASM-based dropper patterns (js-logger-pack)
check_wasm_dropper() {
header "WASM dropper patterns in install scripts"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "WASM dropper" "⏭️"; return; }
local found=0
for dir in "${RESOLVED_DIRS[@]}"; do
while IFS= read -r file; do
[[ -z "$file" ]] && continue
if grep -qE "WebAssembly\.instantiate|WebAssembly\.compile" "$file" 2>/dev/null; then
if grep -qE "Buffer\.from\(.*base64|atob\(" "$file" 2>/dev/null; then
warn "WASM + Base64 in: $file"
found=$((found + 1))
fi
fi
done < <(find "$dir" -maxdepth "$MAX_DEPTH" \
-path '*/node_modules' -prune -o \
-path '*/.git' -prune -o \
\( -name 'print.js' -o -name 'install.js' -o -name 'postinstall.js' -o -name 'preinstall.js' -o -name 'setup.js' \) \
-print 2>/dev/null)
done
if [[ $found -gt 0 ]]; then
detail "WASM decoded from Base64 in install scripts — js-logger-pack pattern"
record "WASM dropper" "⚠️"
else
pass "No WASM dropper patterns found"
record "WASM dropper" "✅"
fi
}
# 28. Known malicious npm accounts in lockfiles
check_malicious_npm_accounts() {
header "Known malicious npm account references"
[[ ${#RESOLVED_DIRS[@]} -eq 0 ]] && { pass "No directories to scan"; record "Malicious accounts" "⏭️"; return; }
local hits
hits=$(grep -rl $GREP_EXCLUDES \
--include='pnpm-lock.yaml' --include='package-lock.json' --include='yarn.lock' \
-E "$MALICIOUS_NPM_ACCOUNTS" \
"${RESOLVED_DIRS[@]}" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
local count
count=$(echo "$hits" | wc -l | tr -d ' ')
warn "REFERENCES to known malicious npm accounts in $count lockfile(s)"
echo "$hits" | while IFS= read -r f; do
detail "$f"
grep -oE "$MALICIOUS_NPM_ACCOUNTS" "$f" 2>/dev/null | sort -u | while IFS= read -r acct; do
detail " Account: $acct"
done
done
record "Malicious accounts" "⚠️"
else
pass "No known malicious npm accounts in lockfiles"
record "Malicious accounts" "✅"
fi
}
# ─── Deep checks (macOS only, slow) ──────────────────────────────────────────
check_dns_c2() {
header "DNS queries to C2 domains (last 7 days, macOS only)"
printf " ${DIM}This may take 30-60 seconds...${NC}\n"
local hits
hits=$(log show --last 7d --predicate 'process == "mDNSResponder"' --info 2>/dev/null | \
grep -iE "$C2_DOMAINS|$C2_IPS" | wc -l | tr -d ' ')
if [[ "$hits" -gt 0 ]]; then
fail "FOUND $hits DNS queries to C2 domains in the last 7 days"
log show --last 7d --predicate 'process == "mDNSResponder"' --info 2>/dev/null | \
grep -iE "$C2_DOMAINS|$C2_IPS" | head -10 | while IFS= read -r line; do detail "$line"; done
record "DNS to C2" "🔴"
else
pass "No DNS queries to known C2 domains"
record "DNS to C2" "✅"
fi
}
check_syslog_c2() {
header "System log C2 references (last 7 days, macOS only)"
printf " ${DIM}This may take 30-60 seconds...${NC}\n"
local hits
hits=$(log show --last 7d --predicate \
"eventMessage CONTAINS \"trongrid\" OR \
eventMessage CONTAINS \"aptoslabs\" OR \
eventMessage CONTAINS \"166.88.54\" OR \
eventMessage CONTAINS \"198.105.127\" OR \
eventMessage CONTAINS \"23.27.202.27\" OR \
eventMessage CONTAINS \"44.206.172.239\" OR \
eventMessage CONTAINS \"195.201.194.107\" OR \
eventMessage CONTAINS \"bsc-dataseed\" OR \
eventMessage CONTAINS \"api.telegram.org\" OR \
eventMessage CONTAINS \"default-configuration.vercel\" OR \
eventMessage CONTAINS \"vscode-settings-bootstrap.vercel\" OR \
eventMessage CONTAINS \"coingecko-liard.vercel\" OR \
eventMessage CONTAINS \"jrodacooker\" OR \
eventMessage CONTAINS \"260120.vercel\"" 2>/dev/null | \
grep -vE "^Timestamp|^$|^---" | wc -l | tr -d ' ')
if [[ "$hits" -gt 0 ]]; then
fail "FOUND $hits system log entries referencing C2"
record "Syslog C2" "🔴"
else
pass "No C2 references in system logs"
record "Syslog C2" "✅"
fi
}
# ─── Summary ──────────────────────────────────────────────────────────────────
print_summary() {
printf "\n"
printf "══════════════════════════════════════════════════════════════════\n"
printf " SUMMARY\n"
printf "══════════════════════════════════════════════════════════════════\n\n"
printf " %-4s %-35s %s\n" "#" "Check" "Result"
printf " %-4s %-35s %s\n" "---" "-----------------------------------" "------"
for i in "${!CHECK_NAMES[@]}"; do
printf " %-4s %-35s %s\n" "$((i + 1))" "${CHECK_NAMES[$i]}" "${CHECK_RESULTS[$i]}"
done
printf "\n"
if [[ $FINDINGS -gt 0 ]]; then
printf "══════════════════════════════════════════════════════════════════\n"
printf " ${RED}${BOLD}%d FINDING(S) — review the details above${NC}\n" "$FINDINGS"
printf "══════════════════════════════════════════════════════════════════\n"
printf "\n"
printf " 🔴 = confirmed compromise indicator. Stop all work, rotate secrets.\n"
printf " ⚠️ = suspicious, needs manual verification.\n"
printf " 👀 = informational, review for anything unexpected.\n"
printf "\n"
printf " If you see 🔴 findings:\n"
printf " 1. DO NOT push to any repository\n"
printf " 2. DO NOT delete evidence files\n"
printf " 3. Report to your security lead with this output\n"
printf " 4. Rotate ALL secrets (npm tokens, SSH keys, API keys, .env values)\n"
else
printf "══════════════════════════════════════════════════════════════════\n"
printf " ${GREEN}${BOLD}ALL CLEAR — no compromise indicators found${NC}\n"
printf "══════════════════════════════════════════════════════════════════\n"
printf "\n"
printf " 👀 items above are informational — review them briefly.\n"
printf " This scan covers known IOCs as of %s.\n" "$VERSION"
printf " A clean scan does not guarantee absence of novel threats.\n"
fi
printf "\n"
}
# ─── Main ─────────────────────────────────────────────────────────────────────
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--scan-dirs) SCAN_DIRS="$2"; shift 2 ;;
-h|--help)
printf "Usage: %s [--scan-dirs \"/path/a /path/b\"]\n" "$0"
printf " --scan-dirs Override source code directories to scan\n"
exit 0
;;
*) printf "Unknown option: %s\n" "$1"; exit 1 ;;
esac
done
}
main() {
parse_args "$@"
printf "══════════════════════════════════════════════════════════════════\n"
printf " Supply Chain Malware Scanner v%s\n" "$VERSION"
printf " PolinRider / InvisibleFerret / Megalodon\n"
printf "══════════════════════════════════════════════════════════════════\n"
printf "\n"
printf " Platform: %s %s\n" "$(uname -s)" "$(uname -m)"
printf " User: %s\n" "$(whoami)"
printf " Date: %s\n" "$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
resolve_dirs
if [[ ${#RESOLVED_DIRS[@]} -gt 0 ]]; then
printf " Scan dirs: %s\n" "${RESOLVED_DIRS[*]}"
else
printf " Scan dirs: (none found)\n"
fi
# Core IOCs
check_lock_file
check_credential_dumps
check_network_c2
check_invisibleferret
# Source code infection
check_malware_signatures
check_long_config_lines
check_fake_fonts
check_malicious_packages
check_vscode_tasks
check_solana_c2
# Supply chain
check_axios_compromised
check_nx_console
check_postinstall_scripts
# Propagation
check_propagation_artifacts
check_megalodon_actions
# Environment integrity
check_git_hooks
check_npmrc_tokens
check_global_packages
check_extensions
check_persistence
# Additional checks (v1.1)
check_embedded_tools
check_create_require
check_ssh_keys
# New checks (v1.2 — indece report 2026-04)
check_malicious_launchagents
check_injected_ssh_keys
check_prototype_poisoning
check_wasm_dropper
check_malicious_npm_accounts
# Deep checks (macOS only, slow)
if [[ "$(uname)" == "Darwin" ]]; then
check_dns_c2
check_syslog_c2
fi
print_summary
if [[ $FINDINGS -gt 0 ]]; then
exit 1
else
exit 0
fi
}
main "$@"
#Requires -Version 5.1
<#
.SYNOPSIS
Supply Chain Malware Scanner — Windows edition
.DESCRIPTION
Scans for PolinRider, InvisibleFerret, Megalodon, and compromised npm packages.
Read-only: does NOT modify any files or system state.
Safe: never prints credential or token values.
.PARAMETER ScanDirs
Directories to scan for source code. Default: common locations under $HOME.
.EXAMPLE
.\Scan-SupplyChain.ps1
.\Scan-SupplyChain.ps1 -ScanDirs "C:\code", "D:\projects"
#>
[CmdletBinding()]
param(
[string[]]$ScanDirs
)
$ErrorActionPreference = "Continue"
$Version = "1.2.0"
# ─── Configuration ────────────────────────────────────────────────────────────
$DefaultScanDirs = @(
"$env:USERPROFILE\git",
"$env:USERPROFILE\projects",
"$env:USERPROFILE\dev",
"$env:USERPROFILE\code",
"$env:USERPROFILE\repos",
"$env:USERPROFILE\source"
)
if (-not $ScanDirs) { $ScanDirs = $DefaultScanDirs }
$ResolvedDirs = $ScanDirs | Where-Object { Test-Path $_ }
$MaxDepth = 6
$C2Ips = @("166.88.54.158", "198.105.127.210", "23.27.202.27", "44.206.172.239", "195.201.194.107", "107.189.20.115", "216.126.237.71", "216.126.224.220", "95.216.26.109", "165.140.86.52", "66.235.175.48", "38.92.47.157", "45.140.184.41", "136.0.9.8", "154.91.0.196", "23.27.20.143", "85.239.62.36", "83.168.68.219", "166.88.4.2", "23.27.120.142")
$C2DomainsRegex = "trongrid|aptoslabs|bsc-dataseed|bsc-rpc\.publicnode|api\.telegram\.org|coingecko-liard\.vercel|jsonkeeper\.com|api-sub\.jrodacooker|api\.npoint\.io|jsonspack|nexafilm|deoft|pricesheet|mywalletsss|regioncheck\.xyz"
$C2VercelRegex = "default-configuration\.vercel|vscode-settings-bootstrap\.vercel|vscode-load-config\.vercel|260120\.vercel|coingecko-liard\.vercel|cloudflareinsights\.vercel|axioshealthcheck\.vercel|logkit-tau\.vercel|wallet-management-tg-bot\.vercel|polymarkettrading\.vercel|npmjs-doc-builder\.vercel|chalk-logger\.vercel|cloudflare-protection\.vercel|cloudflarefirewall\.vercel|cloudflareguard\.vercel|cloudflaresecurity\.vercel|cloudflareshield\.vercel|locate-my-ip\.vercel|vscode-config-settings\.vercel|vscode-extension-260120\.vercel|vscode-settings-config\.vercel|vscode-extensions-bootstrap\.vercel|davhub88\.vercel|codeviewer-three\.vercel|coreviewer\.vercel|vscode-helper171\.vercel|task-hrec\.vercel|vscode-bootstrapper\.vercel|vscode-production-setting\.vercel|vscode-toolkit-settings\.vercel|tailwind-version-4\.vercel|vscode-ext-git\.vercel|thopywork\.vercel|isvalid-regions\.vercel|ext-checkedin\.vercel|data-kappa\.vercel|apiscriptv3\.vercel|server-check-genimi\.vercel|server-genimi-check\.vercel"
$MaliciousPkgsRegex = "tailwindcss-style-animate|tailwind-mainanimation|tailwind-tionbased|tailwindcss-typography-style|tailwindcss-style-modify|tailwindcss-animate-style|crypto-keccak-js|simple-auth-basic|swplayer-react-sl|tailwind-typography-cssstyle|tailwindcss-style-typography|tailwind-stylecss-typography|tailwind-typ|tailwindthml-flips|tailwind-lines-clamp|chai-use-chains|trgrip|js-logger-pack|tailwind-animationbasic|tailwind-configuration|tailwind-scroller|tailwind-text-fill|tailwindcss-fonttype-inter|tailwindcss-typeface-inter|chai-as-adapter|chai-as-chain-v2|chai-as-char|chai-as-elevated|chai-as-encrypted|chai-as-evm|chai-as-hooked|chai-as-ide|chai-as-init|chai-as-inserted|chai-as-mobj|chai-as-nobj|chai-as-optimized|chai-as-refined|chai-as-type|chai-beta|chai-chain-coremesh|chai-extensions-extras|chalk-ts-logger|cli-pretty-logger|color-cli-log|dev-log-core|jonas-prettier-logger|log-upgrade|pino-pretty-logs|prettier-logger|terminal-structured-logger|express-flowlimit|winston-prism|vite-enhancer-config|request-js-validator|mongoose-lean-hooks|polymarket-onchain-sdk|mexc-utils-helper|sol-sdk|ts-relayer-client|relion-chain|metrify-chain|trackora-chain|bundrix|argonflux|byteboxlab|byteutilsbox|cookie-parseflow|coremesh|express-auth-basic|express-lib-validator|express-session-validator|json-spectaculation|jsontoken-extend|levex-press|peptide-score|peptideenv|rollup-plugin-polyfill-route|vime-azl|wime-zle|atddevs|farm-orchestrator|farm-runner|df-vision|appclaw"
$MaliciousNpmAccounts = "msafe-ci|cubistengineer|ficara9076|calebguo|wakiye8356|codeforge1144|lancer110|lancer117|tailwind11995|mwai2006|joshsingh|jpeek868|jsonspack|corvettdan1963|chainlence|vynlence|koldavich|ronaldobon"
$MaliciousSshKey = "bink@DESKTOP-N8JGD6T"
$ExcludeDirs = @("node_modules", ".next", "dist", ".git", "build", ".turbo", ".cache")
# ─── State ────────────────────────────────────────────────────────────────────
$script:CheckNames = @()
$script:CheckResults = @()
$script:Findings = 0
$script:Current = 0
$script:TotalChecks = 30
# ─── Helpers ──────────────────────────────────────────────────────────────────
function Write-Header($text) {
$script:Current++
Write-Host ""
Write-Host "[$($script:Current)/$($script:TotalChecks)] $text" -ForegroundColor Cyan
}
function Write-Pass($text) { Write-Host " ✅ $text" -ForegroundColor Green }
function Write-Warn($text) { Write-Host " ⚠️ $text" -ForegroundColor Yellow; $script:Findings++ }
function Write-Fail($text) { Write-Host " 🔴 $text" -ForegroundColor Red; $script:Findings++ }
function Write-Detail($text) { Write-Host " $text" -ForegroundColor DarkGray }
function Record($name, $result) {
$script:CheckNames += $name
$script:CheckResults += $result
}
function Get-SourceFiles {
param([string]$Pattern, [string[]]$Include)
$results = @()
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -File -Include $Include -ErrorAction SilentlyContinue |
Where-Object {
$path = $_.FullName
-not ($ExcludeDirs | Where-Object { $path -match "[\\/]$_[\\/]" })
} |
Select-String -Pattern $Pattern -List -ErrorAction SilentlyContinue |
ForEach-Object { $results += $_.Path }
}
return $results
}
# ─── Checks ───────────────────────────────────────────────────────────────────
function Test-LockFile {
Write-Header "Lock file (payload execution marker)"
$lockFile = "$env:LOCALAPPDATA\Temp\tmp7A863DD1.tmp"
if (Test-Path $lockFile) {
Write-Fail "LOCK FILE EXISTS at $lockFile — payload executed"
Write-Detail (Get-Item $lockFile | Format-List | Out-String)
Record "Lock file" "🔴"
} else {
Write-Pass "Not found"
Record "Lock file" "✅"
}
}
function Test-CredentialDumps {
Write-Header "Credential dumps (~/.npm staging)"
$npmDir = "$env:USERPROFILE\.npm"
$suspicious = @("_credentials.json", "_sysenv.json", "_sysenv.env", "_info.json")
$found = @()
if (Test-Path $npmDir) {
$found += Get-ChildItem -Path $npmDir -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Name -in $suspicious -or $_.Extension -eq ".zip" }
}
if ($found.Count -gt 0) {
Write-Fail "CREDENTIAL DUMPS FOUND — $($found.Count) file(s)"
$found | ForEach-Object { Write-Detail "$($_.FullName) ($($_.Length) bytes, $($_.LastWriteTime))" }
Write-Detail "DO NOT delete — forensic evidence"
Record "Credential dumps" "🔴"
} else {
Write-Pass "No suspicious files"
Record "Credential dumps" "✅"
}
}
function Test-NetworkC2 {
Write-Header "Active C2 network connections"
$hits = netstat -ano 2>$null | Select-String ($C2Ips -join "|")
if ($hits) {
Write-Fail "ACTIVE CONNECTION TO C2"
$hits | ForEach-Object { Write-Detail $_.Line }
Record "C2 connections" "🔴"
} else {
Write-Pass "No active connections to known C2 IPs"
Record "C2 connections" "✅"
}
}
function Test-InvisibleFerret {
Write-Header "InvisibleFerret RAT processes"
$pyProcs = Get-CimInstance Win32_Process -Filter "Name='python.exe' OR Name='python3.exe'" -ErrorAction SilentlyContinue |
Where-Object { $_.CommandLine -match "__DAEMONIZED|cat\.py" }
if ($pyProcs) {
Write-Fail "INVISIBLEFERRET PROCESS DETECTED"
$pyProcs | ForEach-Object { Write-Detail "PID $($_.ProcessId): $($_.CommandLine)" }
Record "InvisibleFerret" "🔴"
} else {
Write-Pass "No InvisibleFerret processes found"
Record "InvisibleFerret" "✅"
}
}
function Test-MalwareSignatures {
Write-Header "Known malware signatures in source files"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Malware signatures" "⏭️"; return }
$pattern = 'rmcej%otb%|Cot%3t=shtP|_\$_[a-f0-9]{4}|_0x[a-f0-9]{4}|global\[''\w{1,3}''\]\s*=|globalThis\[''\w{1,3}''\]\s*='
$hits = Get-SourceFiles -Pattern $pattern -Include @("*.js", "*.mjs", "*.cjs", "*.ts")
if ($hits.Count -gt 0) {
Write-Fail "MALWARE SIGNATURES in $($hits.Count) file(s)"
$hits | Select-Object -First 20 | ForEach-Object { Write-Detail $_ }
Record "Malware signatures" "🔴"
} else {
Write-Pass "No known signatures found"
Record "Malware signatures" "✅"
}
}
function Test-LongConfigLines {
Write-Header "Suspiciously long config lines (>200 chars)"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Long config lines" "⏭️"; return }
$configNames = @(
"postcss.config.*", "next.config.*", "tailwind.config.*", "eslint.config.*",
"babel.config.*", "jest.config.*", "vite.config.*", "tsup.config.*",
"astro.config.*", "webpack.config.*", "vue.config.*", "App.js"
)
$found = @()
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -File -Include $configNames -ErrorAction SilentlyContinue |
Where-Object {
$path = $_.FullName
-not ($ExcludeDirs | Where-Object { $path -match "[\\/]$_[\\/]" })
} | ForEach-Object {
$longest = (Get-Content $_.FullName -ErrorAction SilentlyContinue | ForEach-Object { $_.Length } | Measure-Object -Maximum).Maximum
if ($longest -gt 200) {
$found += [PSCustomObject]@{ Path = $_.FullName; Length = $longest }
}
}
}
if ($found.Count -gt 0) {
Write-Warn "FOUND $($found.Count) config file(s) with suspiciously long lines"
$found | ForEach-Object { Write-Detail "⚠️ SUSPICIOUS ($($_.Length) chars): $($_.Path)" }
Write-Detail "Payload hides after ~1300 trailing spaces — inspect manually"
Record "Long config lines" "⚠️"
} else {
Write-Pass "All config files have normal line lengths"
Record "Long config lines" "✅"
}
}
function Test-FakeFonts {
Write-Header "Fake font payloads (.woff2 with JS)"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Fake fonts" "⏭️"; return }
$found = 0
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -File -Filter "*.woff2" -ErrorAction SilentlyContinue |
Where-Object {
$path = $_.FullName
-not ($ExcludeDirs | Where-Object { $path -match "[\\/]$_[\\/]" })
} | ForEach-Object {
$bytes = [System.IO.File]::ReadAllBytes($_.FullName) | Select-Object -First 100
$text = [System.Text.Encoding]::ASCII.GetString($bytes)
if ($text -match "global|function|require|module\.exports") {
Write-Warn "JS payload in font: $($_.FullName)"
$found++
}
}
}
if ($found -eq 0) {
Write-Pass "No fake font payloads found"
Record "Fake fonts" "✅"
} else {
Record "Fake fonts" "⚠️"
}
}
function Test-MaliciousPackages {
Write-Header "Malicious npm packages in package.json"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Malicious packages" "⏭️"; return }
$hits = Get-SourceFiles -Pattern $MaliciousPkgsRegex -Include @("package.json")
if ($hits.Count -gt 0) {
Write-Fail "MALICIOUS PACKAGES in $($hits.Count) file(s)"
$hits | ForEach-Object { Write-Detail $_ }
Record "Malicious packages" "🔴"
} else {
Write-Pass "No known malicious packages"
Record "Malicious packages" "✅"
}
}
function Test-VsCodeTasks {
Write-Header "VS Code tasks.json with C2 domains"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "tasks.json C2" "⏭️"; return }
$hits = Get-SourceFiles -Pattern "$C2VercelRegex|e9b53a7c-2342-4b15-b02d-bd8b8f6a03f9" -Include @("tasks.json")
if ($hits.Count -gt 0) {
Write-Fail "MALICIOUS tasks.json FOUND"
$hits | ForEach-Object { Write-Detail $_ }
Record "tasks.json C2" "🔴"
} else {
Write-Pass "No malicious tasks.json"
Record "tasks.json C2" "✅"
}
}
function Test-SolanaC2 {
Write-Header "Solana/blockchain C2 patterns in source"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Blockchain C2" "⏭️"; return }
$hits = Get-SourceFiles -Pattern "@solana/web3|trongrid|wallet.*memo" -Include @("*.js", "*.mjs", "*.cjs", "*.ts")
if ($hits.Count -gt 0) {
Write-Warn "BLOCKCHAIN REFERENCES in $($hits.Count) file(s) — verify if legitimate"
$hits | Select-Object -First 10 | ForEach-Object { Write-Detail $_ }
Record "Blockchain C2" "⚠️"
} else {
Write-Pass "No blockchain C2 patterns"
Record "Blockchain C2" "✅"
}
}
function Test-AxiosCompromised {
Write-Header "Compromised axios versions"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Axios versions" "⏭️"; return }
$hits = Get-SourceFiles -Pattern '"axios":\s*"(1\.14\.1|0\.30\.4)"|axios@(1\.14\.1|0\.30\.4)' `
-Include @("package.json", "pnpm-lock.yaml", "package-lock.json", "yarn.lock")
if ($hits.Count -gt 0) {
Write-Fail "COMPROMISED AXIOS VERSION"
$hits | ForEach-Object { Write-Detail $_ }
Record "Axios versions" "🔴"
} else {
Write-Pass "No compromised axios versions"
Record "Axios versions" "✅"
}
}
function Test-NxConsole {
Write-Header "Nx Console extension (CVE-2026-48027)"
$exts = ""
try { $exts = & code --list-extensions --show-versions 2>$null } catch {}
if (-not $exts) { try { $exts = & cursor --list-extensions --show-versions 2>$null } catch {} }
if ($exts -match "nrwl|nx-console") {
$match = ($exts | Select-String "nrwl|nx-console" | Select-Object -First 1).Line
if ($match -match "18\.95\.0") {
Write-Fail "COMPROMISED Nx Console v18.95.0 — update to 18.100.0+"
Record "Nx Console" "🔴"
} else {
Write-Pass "Nx Console installed, version safe: $match"
Record "Nx Console" "✅"
}
} else {
Write-Pass "Nx Console not installed"
Record "Nx Console" "✅"
}
}
function Test-PostinstallScripts {
Write-Header "Suspicious postinstall/preinstall scripts"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "postinstall" "⏭️"; return }
$safePatterns = @("prisma", "patch-package", "husky", "ngcc", "electron-rebuild",
"node-gyp", "esbuild", "tsc ", "tsx ", "npx prisma", "playwright", "puppeteer",
"opencollective", "is-ci", "npm run build", "pnpm run build", "yarn build",
"expo-configure", "pod-install")
$found = 0
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -Depth 4 -File -Filter "package.json" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch "[\\/]node_modules[\\/]" } |
ForEach-Object {
try {
$pkg = Get-Content $_.FullName -Raw | ConvertFrom-Json
foreach ($key in @("postinstall", "preinstall")) {
$script = $pkg.scripts.$key
if ($script) {
$isSafe = $safePatterns | Where-Object { $script -match [regex]::Escape($_) }
if (-not $isSafe) {
Write-Warn "REVIEW: $($_.FullName)"
$redacted = $script -replace '(Bearer |[Tt]oken[=: ]+|_TOKEN[=: ]+|_KEY[=: ]+|_SECRET[=: ]+|_PASSWORD[=: ]+)[A-Za-z0-9._\-]{8,}', '$1[REDACTED]'
Write-Detail "${key}: $redacted"
$found++
}
}
}
} catch {}
}
}
if ($found -eq 0) {
Write-Pass "No suspicious install scripts"
Record "postinstall" "✅"
} else {
Record "postinstall" "⚠️"
}
}
function Test-PropagationArtifacts {
Write-Header "Propagation artifacts (.bat files)"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Propagation" "⏭️"; return }
$found = 0
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -in @("temp_auto_push.bat", "config.bat") -and $_.FullName -notmatch "[\\/]node_modules[\\/]" } |
ForEach-Object {
Write-Warn "Propagation artifact: $($_.FullName)"
$found++
}
}
if ($found -eq 0) {
Write-Pass "No propagation artifacts"
Record "Propagation" "✅"
} else {
Record "Propagation" "⚠️"
}
}
function Test-MegalodonActions {
Write-Header "Megalodon GitHub Actions injection"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "GH Actions" "⏭️"; return }
$found = 0
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -File -Include @("*.yml", "*.yaml") -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "\.github[\\/]workflows" } |
ForEach-Object {
$content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
if ($content -match 'curl\s.*\|\s*(sh|bash)|wget\s.*\|\s*(sh|bash)|base64\s+(-d|--decode)\s*\||\beval\s+\$\(') {
Write-Warn "Suspicious pattern in: $($_.FullName)"
$found++
}
}
}
if ($found -eq 0) {
Write-Pass "No suspicious Actions patterns"
Record "GH Actions" "✅"
} else {
Record "GH Actions" "⚠️"
}
}
function Test-GitHooks {
Write-Header "Git hooks integrity"
$found = 0
$hooksPath = git config --global core.hooksPath 2>$null
if ($hooksPath) {
Write-Warn "Global hooks path: $hooksPath"
$found++
} else {
Write-Pass "No global hooksPath override"
}
if ($found -eq 0) { Record "Git hooks" "✅" } else { Record "Git hooks" "⚠️" }
}
function Test-NpmrcTokens {
Write-Header "npm auth tokens (.npmrc)"
$npmrc = "$env:USERPROFILE\.npmrc"
if (Test-Path $npmrc) {
$tokenLines = (Get-Content $npmrc | Select-String "authToken|_auth|_password").Count
if ($tokenLines -gt 0) {
Write-Warn ".npmrc contains $tokenLines line(s) with auth tokens"
Write-Detail "If compromised, rotate these tokens immediately"
Write-Detail "Token values intentionally hidden — inspect manually"
Record ".npmrc tokens" "⚠️"
} else {
Write-Pass ".npmrc exists but no auth tokens"
Record ".npmrc tokens" "✅"
}
} else {
Write-Pass "No .npmrc file"
Record ".npmrc tokens" "✅"
}
}
function Test-GlobalPackages {
Write-Header "Global packages"
Write-Detail "npm global:"
npm ls -g --depth=0 2>$null | Select-Object -Skip 1 | ForEach-Object { Write-Detail " $_" }
if (Get-Command pnpm -ErrorAction SilentlyContinue) {
Write-Detail "pnpm global:"
pnpm ls -g 2>$null | Select-Object -Skip 1 | ForEach-Object { Write-Detail " $_" }
}
Write-Pass "Review lists above for unfamiliar packages"
Record "Global packages" "👀"
}
function Test-Extensions {
Write-Header "Editor extensions"
$exts = @()
try { $exts = & code --list-extensions 2>$null } catch {}
if (-not $exts) { try { $exts = & cursor --list-extensions 2>$null } catch {} }
if ($exts) {
$exts | ForEach-Object { Write-Detail $_ }
Write-Pass "Review for unfamiliar extensions"
} else {
Write-Pass "No editor extensions found (or CLI not available)"
}
Record "Extensions" "👀"
}
function Test-Persistence {
Write-Header "Persistence mechanisms"
Write-Detail "Scheduled tasks (non-Microsoft):"
Get-ScheduledTask -ErrorAction SilentlyContinue |
Where-Object { $_.Author -notmatch "Microsoft|Windows" -and $_.State -ne "Disabled" } |
Select-Object -First 20 |
ForEach-Object { Write-Detail " $($_.TaskName) — $($_.Author)" }
Write-Detail "Suspicious processes:"
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match "node|npm|npx|python" -and $_.Name -notmatch "Code|Electron|cursor" } |
ForEach-Object { Write-Detail " PID $($_.ProcessId): $($_.CommandLine)" }
Write-Detail "DNS cache (C2 domains):"
$dnsHits = ipconfig /displaydns 2>$null | Select-String "$C2DomainsRegex|$($C2Ips -join '|' -replace '\.','\.')|$C2VercelRegex"
if ($dnsHits) {
$dnsHits | ForEach-Object { Write-Warn "DNS cache hit: $($_.Line.Trim())" }
} else {
Write-Detail " No C2 domains in DNS cache"
}
# Check for malware patterns in running processes
$malwareProcs = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object { $_.CommandLine -match '_\$_[a-f0-9]{4}|_0x[a-f0-9]{4}|global\[|node -e.*global|__DAEMONIZED' }
if ($malwareProcs) {
Write-Fail "MALWARE PROCESS DETECTED — obfuscated code in running process"
$malwareProcs | ForEach-Object { Write-Detail " PID $($_.ProcessId): $($_.CommandLine.Substring(0, [Math]::Min(200, $_.CommandLine.Length)))" }
}
# RAT binary drop
$ratPath = "$env:LOCALAPPDATA\Temp\0001.dat"
if (Test-Path $ratPath) {
Write-Fail "RAT BINARY DROP: $ratPath"
Write-Detail (Get-Item $ratPath | Format-List | Out-String)
}
if ($malwareProcs -or (Test-Path $ratPath)) {
Record "Persistence" "🔴"
} else {
Write-Pass "Review items above"
Record "Persistence" "👀"
}
}
function Test-EmbeddedTools {
Write-Header "Embedded scanning tool binaries"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Embedded tools" "⏭️"; return }
$found = 0
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -File -ErrorAction SilentlyContinue |
Where-Object {
$_.Name -match "^(trufflehog|gitleaks|detect-secrets)" -and
$_.FullName -notmatch "[\\/](node_modules|\.git)[\\/]"
} | ForEach-Object {
Write-Warn "Embedded scanning tool: $($_.FullName)"
Write-Detail "$($_.Length) bytes, $($_.LastWriteTime)"
$found++
}
}
if ($found -eq 0) {
Write-Pass "No embedded scanning tool binaries"
Record "Embedded tools" "✅"
} else {
Write-Detail "Committed scanning binaries could be trojaned — verify provenance"
Record "Embedded tools" "⚠️"
}
}
function Test-CreateRequire {
Write-Header "createRequire(import.meta) in application code"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "createRequire" "⏭️"; return }
$hits = Get-SourceFiles -Pattern 'createRequire\(import\.meta' -Include @("*.js", "*.mjs", "*.ts")
if ($hits.Count -gt 0) {
$suspicious = 0
foreach ($f in $hits) {
if ($f -match '\.config\.|scripts[\\/]|bin[\\/]|cli|build|webpack|rollup|vite|esbuild|tsup') {
Write-Detail "Expected (tooling): $f"
} else {
Write-Warn "Suspicious createRequire: $f"
$suspicious++
}
}
if ($suspicious -eq 0) {
Write-Pass "createRequire found only in expected tooling files"
Record "createRequire" "✅"
} else {
Write-Detail "createRequire can load native binaries or bypass ESM — verify usage"
Record "createRequire" "⚠️"
}
} else {
Write-Pass "No createRequire(import.meta) found"
Record "createRequire" "✅"
}
}
function Test-SshKeys {
Write-Header "SSH key audit (~/.ssh)"
$sshDir = "$env:USERPROFILE\.ssh"
if (-not (Test-Path $sshDir)) {
Write-Pass "~/.ssh does not exist"
Record "SSH keys" "✅"
return
}
$found = 0
if (Test-Path "$sshDir\authorized_keys") {
$keyCount = (Get-Content "$sshDir\authorized_keys" -ErrorAction SilentlyContinue | Measure-Object -Line).Lines
Write-Detail "authorized_keys: $keyCount key(s)"
try {
ssh-keygen -lf "$sshDir\authorized_keys" 2>$null | ForEach-Object { Write-Detail " $_" }
} catch {}
}
Write-Detail "File modification times:"
$recentThreshold = (Get-Date).AddHours(-24)
Get-ChildItem -Path $sshDir -File -ErrorAction SilentlyContinue | ForEach-Object {
Write-Detail " $($_.LastWriteTime) $($_.Name)"
if ($_.LastWriteTime -gt $recentThreshold) {
Write-Warn "RECENTLY MODIFIED (last 24h): $($_.FullName)"
$found++
}
}
if ($found -eq 0) {
Write-Pass "SSH key state looks normal"
Record "SSH keys" "✅"
} else {
Record "SSH keys" "⚠️"
}
}
# 24. Known malicious scheduled tasks / services
function Test-MaliciousServices {
Write-Header "Known malicious services & scheduled tasks"
$found = 0
# MicrosoftSystem64 service (js-logger-pack on Windows)
$svc = Get-Service -Name "MicrosoftSystem64" -ErrorAction SilentlyContinue
if ($svc) {
Write-Fail "MALICIOUS SERVICE: MicrosoftSystem64 (js-logger-pack)"
Write-Detail "Status: $($svc.Status)"
$found++
}
if ($found -eq 0) {
Write-Pass "No known malicious services"
Record "Malicious services" "✅"
} else {
Record "Malicious services" "🔴"
}
}
# 25. Injected SSH keys (js-logger-pack)
function Test-InjectedSshKeys {
Write-Header "Injected SSH keys (known malware keys)"
$ak = "$env:USERPROFILE\.ssh\authorized_keys"
if (-not (Test-Path $ak)) {
Write-Pass "No authorized_keys file"
Record "Injected SSH keys" "✅"
return
}
$hits = (Get-Content $ak -ErrorAction SilentlyContinue | Select-String $MaliciousSshKey).Count
if ($hits -gt 0) {
Write-Fail "MALICIOUS SSH KEY FOUND in authorized_keys ($MaliciousSshKey)"
Write-Detail "Injected by js-logger-pack malware — remove immediately"
Record "Injected SSH keys" "🔴"
} else {
Write-Pass "No known malicious SSH keys"
Record "Injected SSH keys" "✅"
}
}
# 26. Prototype poisoning & dynamic code execution
function Test-PrototypePoisoning {
Write-Header "Prototype poisoning & dynamic code execution"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Prototype poisoning" "⏭️"; return }
$hits = Get-SourceFiles -Pattern 'Transaction\.prototype\.moveCall|Function\.constructor\("require"|Function\.constructor\(''require''' -Include @("*.js", "*.mjs", "*.cjs", "*.ts")
if ($hits.Count -gt 0) {
Write-Fail "PROTOTYPE POISONING PATTERNS in $($hits.Count) file(s)"
$hits | Select-Object -First 10 | ForEach-Object { Write-Detail $_ }
Write-Detail "Transaction.prototype override = crypto stealer (crypto-keccak-js)"
Write-Detail "Function.constructor('require') = C2 dropper (chai-use-chains)"
Record "Prototype poisoning" "🔴"
} else {
Write-Pass "No prototype poisoning patterns found"
Record "Prototype poisoning" "✅"
}
}
# 27. WASM dropper patterns (js-logger-pack)
function Test-WasmDropper {
Write-Header "WASM dropper patterns in install scripts"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "WASM dropper" "⏭️"; return }
$found = 0
foreach ($dir in $ResolvedDirs) {
Get-ChildItem -Path $dir -Recurse -File -Include @("print.js", "install.js", "postinstall.js", "preinstall.js", "setup.js") -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch "[\\/](node_modules|\.git)[\\/]" } |
ForEach-Object {
$content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
if ($content -match "WebAssembly\.(instantiate|compile)" -and $content -match "Buffer\.from.*base64|atob\(") {
Write-Warn "WASM + Base64 in: $($_.FullName)"
$found++
}
}
}
if ($found -eq 0) {
Write-Pass "No WASM dropper patterns found"
Record "WASM dropper" "✅"
} else {
Write-Detail "WASM decoded from Base64 in install scripts — js-logger-pack pattern"
Record "WASM dropper" "⚠️"
}
}
# 28. Known malicious npm accounts in lockfiles
function Test-MaliciousNpmAccounts {
Write-Header "Known malicious npm account references"
if ($ResolvedDirs.Count -eq 0) { Write-Pass "No directories to scan"; Record "Malicious accounts" "⏭️"; return }
$hits = Get-SourceFiles -Pattern $MaliciousNpmAccounts -Include @("pnpm-lock.yaml", "package-lock.json", "yarn.lock")
if ($hits.Count -gt 0) {
Write-Warn "REFERENCES to known malicious npm accounts in $($hits.Count) lockfile(s)"
$hits | ForEach-Object { Write-Detail $_ }
Record "Malicious accounts" "⚠️"
} else {
Write-Pass "No known malicious npm accounts in lockfiles"
Record "Malicious accounts" "✅"
}
}
# ─── Deep checks (Windows Event Log) ─────────────────────────────────────────
function Test-EventLogNetwork {
Write-Header "Security Event Log — C2 network events (last 7 days)"
Write-Detail "This may take a while..."
try {
$events = Get-WinEvent -FilterHashtable @{
LogName = 'Security'; Id = 5156; StartTime = (Get-Date).AddDays(-7)
} -MaxEvents 10000 -ErrorAction SilentlyContinue |
Where-Object { $_.Message -match "node|npm|python" } |
Where-Object { $_.Message -match "166\.88\.54|198\.105\.127|23\.27\.202|44\.206\.172|195\.201\.194|trongrid|aptoslabs|telegram|coingecko-liard|jrodacooker" }
if ($events) {
Write-Fail "FOUND $($events.Count) C2 network events"
$events | Select-Object -First 10 | ForEach-Object {
Write-Detail "$($_.TimeCreated): $($_.Message | Select-Object -First 1)"
}
Record "Event Log (network)" "🔴"
} else {
Write-Pass "No C2 network events"
Record "Event Log (network)" "✅"
}
} catch {
Write-Detail "Could not read Security log (requires Admin)"
Record "Event Log (network)" "⏭️"
}
}
function Test-EventLogProcess {
Write-Header "Security Event Log — suspicious process creation (last 7 days)"
try {
$events = Get-WinEvent -FilterHashtable @{
LogName = 'Security'; Id = 4688; StartTime = (Get-Date).AddDays(-7)
} -MaxEvents 10000 -ErrorAction SilentlyContinue |
Where-Object { $_.Message -match "taskkill|wmic useraccount|tmp7A863DD1" }
if ($events) {
Write-Fail "SUSPICIOUS PROCESS CREATION EVENTS"
$events | Select-Object -First 10 | ForEach-Object {
Write-Detail "$($_.TimeCreated): $($_.Message | Select-Object -First 1)"
}
Record "Event Log (process)" "🔴"
} else {
Write-Pass "No suspicious process events"
Record "Event Log (process)" "✅"
}
} catch {
Write-Detail "Could not read Security log (requires Admin)"
Record "Event Log (process)" "⏭️"
}
}
# ─── Summary ──────────────────────────────────────────────────────────────────
function Show-Summary {
Write-Host ""
Write-Host "══════════════════════════════════════════════════════════════════" -ForegroundColor White
Write-Host " SUMMARY" -ForegroundColor White
Write-Host "══════════════════════════════════════════════════════════════════" -ForegroundColor White
Write-Host ""
for ($i = 0; $i -lt $script:CheckNames.Count; $i++) {
$num = ($i + 1).ToString().PadLeft(3)
$name = $script:CheckNames[$i].PadRight(35)
Write-Host " $num $name $($script:CheckResults[$i])"
}
Write-Host ""
if ($script:Findings -gt 0) {
Write-Host "══════════════════════════════════════════════════════════════════" -ForegroundColor Red
Write-Host " $($script:Findings) FINDING(S) — review the details above" -ForegroundColor Red
Write-Host "══════════════════════════════════════════════════════════════════" -ForegroundColor Red
Write-Host ""
Write-Host " 🔴 = confirmed compromise indicator. Stop all work, rotate secrets."
Write-Host " ⚠️ = suspicious, needs manual verification."
Write-Host " 👀 = informational, review for anything unexpected."
Write-Host ""
Write-Host " If you see 🔴 findings:" -ForegroundColor Red
Write-Host " 1. DO NOT push to any repository"
Write-Host " 2. DO NOT delete evidence files"
Write-Host " 3. Report to your security lead with this output"
Write-Host " 4. Rotate ALL secrets (npm tokens, SSH keys, API keys, .env values)"
} else {
Write-Host "══════════════════════════════════════════════════════════════════" -ForegroundColor Green
Write-Host " ALL CLEAR — no compromise indicators found" -ForegroundColor Green
Write-Host "══════════════════════════════════════════════════════════════════" -ForegroundColor Green
Write-Host ""
Write-Host " 👀 items are informational — review briefly."
Write-Host " Clean scan does not guarantee absence of novel threats."
}
Write-Host ""
}
# ─── Main ─────────────────────────────────────────────────────────────────────
Write-Host "══════════════════════════════════════════════════════════════════"
Write-Host " Supply Chain Malware Scanner v$Version"
Write-Host " PolinRider / InvisibleFerret / Megalodon"
Write-Host "══════════════════════════════════════════════════════════════════"
Write-Host ""
Write-Host " Platform: Windows $([System.Environment]::OSVersion.Version)"
Write-Host " User: $env:USERNAME"
Write-Host " Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC' -AsUTC)"
Write-Host " Scan dirs: $($ResolvedDirs -join ', ')"
# Core IOCs
Test-LockFile
Test-CredentialDumps
Test-NetworkC2
Test-InvisibleFerret
# Source code infection
Test-MalwareSignatures
Test-LongConfigLines
Test-FakeFonts
Test-MaliciousPackages
Test-VsCodeTasks
Test-SolanaC2
# Supply chain
Test-AxiosCompromised
Test-NxConsole
Test-PostinstallScripts
# Propagation
Test-PropagationArtifacts
Test-MegalodonActions
# Environment integrity
Test-GitHooks
Test-NpmrcTokens
Test-GlobalPackages
Test-Extensions
Test-Persistence
# Additional checks (v1.1)
Test-EmbeddedTools
Test-CreateRequire
Test-SshKeys
# New checks (v1.2 — indece + DPRK campaign)
Test-MaliciousServices
Test-InjectedSshKeys
Test-PrototypePoisoning
Test-WasmDropper
Test-MaliciousNpmAccounts
# Deep checks (Windows Event Log)
Test-EventLogNetwork
Test-EventLogProcess
Show-Summary
if ($script:Findings -gt 0) { exit 1 } else { exit 0 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment