Skip to content

zer0Tokens/PolinRiderScanner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›‘οΈ PolinRider Scanner

Find the North-Korean "Contagious Interview" malware anywhere on your machine. Fast, and without ever running it.

Rust Platforms Read only License


PolinRider Scanner walks your whole computer, reads every file as plain data, and flags the ones that carry fingerprints of the PolinRider malware. It never opens, runs, installs, or require()s anything it looks at, so checking an infected file cannot infect you. It reads and it reports. The only file it writes is a JSON report of what it found.

Indicators shown in this README are "defanged" (evil[.]com, 1.2.3[.]4) so the document is safe to keep around and will not set off the scanner itself.


The problem, in plain English

PolinRider is malware run by a North-Korean crew. It is publicly tracked as BeaverTail, InvisibleFerret, and OtterCookie, part of the "Contagious Interview" operation. It goes after developers:

  1. A fake recruiter or client sends you a "coding challenge," a demo repo, or an npm package to try.
  2. Buried inside is a booby-trapped file. Usually a config file (next.config.mjs, tailwind.config.js, and so on), a fake .woff2 font that is really JavaScript, or a .vscode/tasks.json rigged to run the instant you open the folder.
  3. The moment you run the project, or just open it in VS Code, the payload steals your saved passwords, browser sessions, crypto wallets, SSH keys, and GitHub tokens, then phones home.
  4. With your GitHub token it logs in as you and force-pushes across every repo you can write to. That is how one infected laptop becomes a whole team's problem.

The original research, the tracked victim and repo lists, and the incident-response playbook are kept here: OpenSourceMalware/PolinRider.


Quick start

You need Rust. Then:

git clone https://github.com/zer0Tokens/PolinRiderScanner.git
cd PolinRiderScanner
cargo build --release

Scan the whole machine:

# macOS / Linux, scans /
./target/release/polinrider-scanner

# Windows, scans every drive it finds
.\target\release\polinrider-scanner.exe

Or point it at a folder you are worried about:

./target/release/polinrider-scanner ~/Downloads/that-coding-challenge

Every run prints a live summary and drops a polinrider-report.json next to you with the full details. The exit code is 1 if anything HIGH or MEDIUM turned up and 0 if you are clean, which is handy for CI or a cron job.


What you'll see

β Ή 138204 files Β· 22.1 GB Β· 41233/s Β· 1 flagged Β· …/Downloads/cool-demo/next.config.mjs

──────────────────────────────────────────────────────────────
 PolinRider scan complete
──────────────────────────────────────────────────────────────
 Scanned    412883 files (61.4 GB in 94.2s, 4383/s)
 Unreadable 1204 (permission denied / IO errors)
 Too large  7 (> 1024 MiB, contents skipped)

 ⚠️  2 indicator(s) in 1 file(s):  2 HIGH · 0 MEDIUM · 0 LOW

 [HIGH] content   /Users/you/Downloads/cool-demo/public/fonts/fa-solid-400.woff2  @778
     PolinRider obfuscator v1 signature marker (obf-v1-marker)
     β”‚ ...split(x)})("rmcej%o…",2857687);global[_$_1e4x[0]]= require;if(...

 Report written to polinrider-report.json

A clean machine just says βœ… CLEAN. No PolinRider indicators found.

The JSON report

polinrider-report.json groups everything so you can see which files are hit at a glance, alongside the raw findings:

{
  "tool": "PolinRider Scanner",
  "scanned_files": 412883,
  "infected_file_count": 1,
  "high": 2, "medium": 0, "low": 0,
  "infected_files": [
    {
      "path": "/Users/you/Downloads/cool-demo/public/fonts/fa-solid-400.woff2",
      "worst_severity": "HIGH",
      "indicators": 2,
      "signatures": ["obf-v1-marker", "obf-decoder"]
    }
  ],
  "findings": [ { "path": "…", "severity": "HIGH", "signature_id": "obf-decoder", "offset": 343 } ]
}

How the scanner works

        roots (/, C:\, or a folder you name)
                     β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  parallel directory walk  β”‚   all CPU cores at once. hidden files,
        β”‚  (skips pseudo-FS, self)  β”‚   dotfiles and .gitignore'd files included
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚  one file at a time, read-only
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   name check + byte scan  β”‚   Aho-Corasick: every indicator matched
        β”‚  (single pass per file)   β”‚   in one pass. small files slurped,
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   big files streamed with flat memory
                     β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  rank HIGH / MEDIUM / LOW β”‚ β†’ live progress, console summary,
        β”‚  group by file, report    β”‚   and polinrider-report.json
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

A few choices worth calling out:

  • One pass, many patterns. All of the indicators are compiled into a single Aho-Corasick automaton, so each byte of a file is looked at once no matter how many indicators exist. That is what lets a full-disk scan finish in minutes.
  • No blind spots. The walk ignores .gitignore and hidden-file rules on purpose and descends into node_modules. That is exactly where these payloads like to hide, whether fake fonts, trojaned config files, or malicious deps.
  • Big files cannot hide anything. There is no cap on how many matches we read through, so an attacker cannot bury the real indicator behind megabytes of noise. Files over the size limit are streamed, so memory stays flat.
  • The scanner will not cry wolf on itself. Its indicator list is XOR-obfuscated at rest, so the raw malware strings never sit in this repo's source or the built binary. A full-disk scan will not flag the scanner, its own report, or its checkout, and other antivirus will not quarantine it over the strings.

What it looks for

Category Examples (defanged)
Obfuscator markers rmcej%o…, Cot%3t…, the _$_1e4x decoder
Command-and-control default-configuration[.]vercel[.]app and other bootstrap hosts, 166.88.54[.]158, lianxinxiao[.]com
Dead-drop wallets the crew's hard-coded TRON and Aptos addresses
XOR keys the payload decryption keys
Malicious npm packages tailwindcss-style-…, tailwind-mainanim… and 4 more (the real tailwindcss-animate is not flagged)
Weaponized VS Code template the StakingGame tasks.json UUID and template string
Drop / propagation files temp_auto_push[.]bat, the Windows lock file, credential-dump names

Only known indicators are reported. The tool skips fuzzy heuristics on purpose, so it will not panic you over a normal tasks.json or a legit config.


Options

polinrider-scanner [ROOT]...        folders/drives to scan (default: whole system)
      --exclude <PATH>              skip a directory (repeatable), e.g. a backup drive
      --no-default-excludes         also scan noise-only spots (.claude data, saved reports)
      --json <FILE>                 report path (default: ./polinrider-report.json)
      --no-report                   do not write the JSON report
  -t, --threads <N>                 worker threads (0 = one per CPU core)
      --max-file-mb <MiB>           do not read contents of files bigger than this (default 1024)
      --no-snippet                  omit the context snippet in the console
      --no-progress                 turn off the live progress line
  -h, --help / -V, --version

Reducing false positives

The scanner matches the malware's strings, so it will flag any file that merely quotes an indicator, not just real payloads: security notes, chat logs, IDE search caches, and saved scan reports. To keep results meaningful:

  • Scan where code lives (~/dev, a project folder) rather than all of $HOME, which is full of logs and caches that reference indicators.
  • A few noise-only spots are skipped automatically, .claude/ (Claude Code data) and this tool's own polinrider-report.json. Pass --no-default-excludes to include them.
  • Use --exclude for anything else that's just records, e.g. --exclude ~/Library/Caches --exclude ~/notes.
  • The scanner also skips its own binary, and its indicator database is obfuscated, so it never flags its own source or checkout.

If a hit's path is a log, a cache, a report, or your own security notes, it's a record of the indicator, not an infection. Real payloads show up in code and asset files inside projects (config files, fake fonts, package.json, etc.).


If it finds something

A HIGH or MEDIUM hit means you should treat that machine as compromised. Do not panic-delete the file. Instead, in order:

  1. Do not open, run, npm install, or require() the flagged files.
  2. Pull the machine off the network.
  3. Rotate every credential it touched: GitHub tokens and SSH keys, cloud keys, browser-saved passwords, wallet seed phrases. Assume they are gone.
  4. Back up your data, then reimage. With this malware, deleting one file is not enough.
  5. Follow the full playbook in the OpenSourceMalware/PolinRider incident-response guide.

A LOW hit, like a generically-named config.bat, is worth a look but is often benign, and does not by itself set the non-zero exit code.


Safety guarantees

  • Never executes anything. No exec, no shell-out, no eval, no dynamic loading. The only syscalls it makes on your files are open read-only, read, and close.
  • Never modifies your files. Nothing is written, moved, quarantined, or deleted. The only output is the JSON report, which you can turn off with --no-report.
  • Does not follow symlinks, so it cannot be walked into a loop or out of the area you asked it to scan.
  • Will not trigger cloud downloads. Windows "Files On-Demand" placeholders are detected and skipped instead of being pulled down.

Contributing & what's not covered yet

This scanner matches known, exact indicators. It does not yet flag the generic mechanisms the campaign abuses, for example a .vscode/tasks.json set to auto-run on folder-open ("runOn": "folderOpen" / allowAutomaticTasks), or a font-extension file whose bytes aren't actually a font. Those gaps, and how to close them safely, are written up in docs/DETECTIONS.md.

Contributions go through pull requests, see CONTRIBUTING.md. main is protected: branch off it, open a PR, and let CI run.

Acknowledgements

Big thanks to the team behind OpenSourceMalware/PolinRider, who are actively researching this campaign, tracking compromised accounts and repositories, and publishing updated indicators and incident-response guidance as it evolves. This scanner is only possible because of their work. The indicators it looks for come straight from their reporting, cross-referenced with public threat intelligence from Unit 42 (Palo Alto), Microsoft Security, Silent Push, Cisco Talos, and NVISO.

If you find a new variant or indicator, please contribute it upstream to the OpenSourceMalware project so everyone's detection improves.


License

MIT, see LICENSE. This is a defensive tool. It contains no malicious code, only the fingerprints needed to detect it.

About

Fast, safe Rust scanner that finds DPRK PolinRider / BeaverTail malware by reading files as data, never running them.

Topics

Resources

License

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors