polin-rider-scanner
v1.0.2
Published
Read-only PolinRider / Glassworm supply-chain malware (IOC) scanner — use as a library in CI/pipelines or as a CLI to scan local files and folders
Maintainers
Readme
polin-rider-scanner
Read-only IOC scanner for the Operation PolinRider / Glassworm supply-chain compromise (DPRK threat actor: obfuscated JS loader, blockchain C2, VS Code auto-run, asset-as-code fonts, invisible-Unicode injection, malicious npm packages).
Built to gate automated pipelines — any remote server or process that pulls and builds/processes application code (CI, build, release, deploy jobs, etc.): scan the code a job has pulled, and abort + quarantine if the verdict is unsafe.
Install
npm install polin-rider-scanner # as a dependency (library)
# or run the CLI without installing:
npx polin-rider-scanner <dir>
# or install the CLI globally:
npm install -g polin-rider-scannerRequires Node.js >= 18. ESM-only.
CLI
polin-rider-scanner <dir> [--all] [--git-history] [--json]
# <dir> directory to scan (default: current directory)
# --all also scan deps/build dirs (node_modules, dist, …)
# --git-history also grep git history for content signatures (needs git)
# --json machine-readable result on stdout
# exit 0 = safe, exit 1 = unsafe (HIGH found) or degraded scan
# examples
npx polin-rider-scanner . # scan the current folder
npx polin-rider-scanner ./app --all # include dependencies
npx polin-rider-scanner ./app --json | jq .safeThe non-zero exit on unsafe/degraded lets you gate a pipeline directly:
npx polin-rider-scanner "$CHECKOUT_DIR" --all || exit 1Library
import { scanForMalware } from "polin-rider-scanner";
const r = scanForMalware(sourceDir, { all: true });
if (!r.safe) {
// r.reasons -> HIGH findings; quarantine the checkout and fail the job
throw new Error("PolinRider IOC detected");
}scanForMalware is a pure function: it returns a result object, prints nothing, and
never calls process.exit — the caller decides what to do. TypeScript types are
bundled (scanner.d.ts).
Result object
{
safe, // true ONLY when high === 0 AND not degraded
blocked, // !safe
degraded, // a check could not run (missing perms/tools) -> treat as unsafe
high, med, low,
reasons, // HIGH findings, grouped by label, with file:line lists
warnings, // MED + LOW (advisory)
findings, // raw [{ sev, label, path, line, detail }]
errors, // capability failures (these set degraded)
notes, // informational (skipped large binaries, finding caps) — do NOT degrade
stats, // { files, bytes, elapsedMs }
}Severity model — why all: true is safe and noise-free
The blocking verdict (safe) is driven only by HIGH signatures, which are
malware-specific and do not occur in legitimate code — including across large
dependency trees, where they produce zero false positives. So scanning
node_modules is both safe and desirable (the payload often lands in a dependency
or a config file).
HIGH (blocks the build):
polinrider:injection-marker—global['x']='8-…'loader fingerprintpolinrider:global-require-rebind—global[_$_1e42[0]]=requirepolinrider:decoder-stub/-af—_$_<hex>/_$af<n>obfuscator stubspolinrider:sig-marker— shuffle-input stringsrmcej%otb%,Cot%3t=shtPpolinrider:tron-exfil-query—only_confirmed=true&only_from=trueaddr:*— hardcoded attacker TRON/Aptos dead-drop addressesnpm:malicious-package— known PolinRider npm packagesfake-asset/fake-asset-payload— font/image with wrong magic bytes carrying JS, or a real binary asset with an embedded loader signature (the.woff2vector)known-malware-hash— known sample SHA-256vscode:autorun-folderOpen— a.vscode/tasks.jsontask withrunOn:"folderOpen"git-history:IOC— content signature in git history (only with--git-history)
MED / LOW (advisory — listed in warnings, do not block):
blockchain C2 endpoints (legit in real web3 code), shuffle-seed constants,
allowAutomaticTasks, off-screen padded-payload lines, Glassworm invisible-Unicode
in source files, fromCharCode(127), eth_getTransactionByHash.
What changed from the earlier scanner (v1) and why
node-runs-assettext grep removed. It matched a Markdown shields.io badge (node version … jest-pnp-resolver.svg) in a dependency README and hard-blocked a build. Replaced by a magic-byte asset check that cannot match prose/URLs.- Bare
folderOpengrep removed. It matched a react-native-vector-icons glyph. Replaced by parsing.vscode/tasks.jsonfor a realrunOn:"folderOpen"task. - Git force-push / commit-date heuristics removed. Legitimate force-pushes and
rebases produced false flags. Detection is by file content signature instead;
--git-historygreps history for the same content signatures (no force-push count). - Standard RPC strings demoted (
eth_getTransactionByHash, bare endpoints) to advisory, so legit blockchain code doesn't fail the build. - Fail-closed, but precisely. Real capability failures (unreadable files, missing
git when
--git-historyis set) setdegradedand make the scan unsafe. Skipped oversized binaries (.aab/.apk/.node/.zip/…) are notes, not errors — they can't hold a line-based payload, so they don't degrade the verdict.
Notes / limitations
- Synchronous and single-threaded; a full
--allscan of a large tree (incl.node_modules) takes tens of seconds to a few minutes. On a latency-sensitive path, run it in a worker thread / child process. - Symlinked directories are not followed (avoids loops); symlinks are skipped.
- Native compiler / IDE output dirs are pruned in every mode (incl.
--all) and recorded asnotes(non-degrading):DerivedData,buildDerivedData,dSYMs, and*.xcbuilddata/*.dSYM/*.xcarchive/*.xcresult. They hold compiled binaries derived from source that is still scanned, so an injected loader is caught upstream; pruning them stops a post-build tree from fail-closing on an unscannable multi-MB build manifest. Genericdist/build/lib/outare not pruned under--all— published npm packages ship executable code there, and that is the primary supply-chain vector. - Oversized files (>
maxFileBytes, default 24 MiB) are not content-scanned. - A clean result covers KNOWN IOCs and is not an absolute guarantee.
References
- https://medium.com/@edawarekaro/operation-polinrider-the-complete-developers-guide-to-detection-containment-and-recovery-c3454bd660e3
- https://opensourcemalware.com/blog/polinrider-attack
- https://github.com/OpenSourceMalware/PolinRider
