
PolinRider is A/B Testing its Way Past Your Detections
Opensourcemalware.com traces PolinRider, a DPRK-linked supply-chain malware delivered via a poisoned .vscode/tasks.json that autoruns an obfuscated payload disguised as a Font Awesome .woff2 file, through an 8-month infection of three real GitHub repos (Binary-Mindz org). Using GitHub's server-side activity log they reconstruct 853 force-pushes showing the operators A/B-testing multiple concurrent payload variants (400/500/900), rewriting commits to impersonate legitimate cleanup commits, hiding payloads inside eslint.config.mjs and prisma.config.ts, and using an Ethereum-wallet-based C2 dead-drop (NullReceiver). The post lists why common detection heuristics (filename search, whitespace padding, marker strings) fail against the new variants and gives working detections (file-type checks, folderOpen task grepping, activity-log force-push monitoring) plus full IOCs.
What to do
The summary says the post "gives working detections" but not what they are. These are the commands, verbatim, plus the specifics that make them work.
Identify fake fonts by content, not name. Every PolinRider fake font is plain text; a real WOFF2 reports as "Web Open Font Format (Version 2)", the payloads as "ASCII text". This survives the 900 rename to a genuine Font Awesome filename.
find . -name '*.woff2' -exec file {} + | grep -v 'Web Open Font'Find folderOpen tasks, hidden or not:
grep -rl --include=tasks.json '"runOn": "folderOpen"' .Parse tasks.json only with a JSONC-tolerant parser — some samples carry a trailing comma that makes strict jq fail and skip the file. Treat a parse error as a reason to look harder.
Scan every branch from a bare clone (reads files without writing them to disk or letting VS Code open them). Code search only indexes the default branch; payloads have been found only on stale branches elsewhere.
git clone --bare https://github.com/OWNER/REPO.git repo.git
cd repo.git
git grep -l -E 'folderOpen|allowAutomaticTasks' $(git for-each-ref --format='%(refname)' refs/heads) -- .vscode
git grep -l -E 'temp_auto_push\.bat|branch_structure\.json' $(git for-each-ref --format='%(refname)' refs/heads) -- .gitignoreCheck the server-side activity log for force-push bursts — a sweep across every branch within seconds from your own team's accounts is the clearest infected-contributor signal:
curl -s "https://api.github.com/repos/OWNER/REPO/activity?activity_type=force_push&per_page=100"Treat lint/ORM/build configs as code. eslint.config.mjs, prisma.config.ts, next.config.js, vite.config.ts all execute on load — no VS Code task needed (npm run lint, a pre-commit hook, or CI runs the appended payload line). Look for content after the expected export.
The machine fingerprint the summary omits
Infected machines leave an uncommitted .gitignore block present in every infected org they've examined:
config.bat
temp_auto_push.bat
temp_interactive_push.bat
branch_structure.jsonIts presence means at least one contributor's machine is infected even if no payload has landed in that repo yet. This is the load-bearing detail behind the post's central operational point: cleaning the repo doesn't clean the machines. All three Binary-Mindz cleanups were force-pushed back — the fastest in five hours — from a different contributor's account, reusing the cleanup commit's own title. Two commits titled as malware removals contained the malware; one ("security: remove VS Code supply-chain malware") is still HEAD and still infected.
Why commit-date triage lies
The auto-push tool copies the hijacked commit's author name, email, message, and usually committer timestamp, then force-pushes. One "Fix ESLint configuration syntax" commit was rewritten across 116 SHAs. Sorting history by date dates a September 25 payload to May 21. Committer names don't match the pushing account ("Mizanur" pushed by 12 accounts); committer timezones are −0700/−0800 while the team works +0600.
Caveats
- Branch protection blocking force-pushes / requiring signed commits would have stopped every rewrite (none were signed) — but only on enabled branches, and rewrites hit every branch, so protecting
mainalone is insufficient. It also fails if pushing accounts can bypass the rules. - Turning off VS Code's auto-run-tasks setting stops the payload firing on folderOpen but does not remove the malware.
- The 900 variant's C2 was not decoded; the authors decline to confirm the TRON/Aptos/BSC resolution until verified for this sample.
Key IOCs
- URLs:
hxxps://260120.vercel[.]app/settings/{linux,win}?flag=9-test - Ethereum dead-drop wallet (NullReceiver, 500 variant):
0xa322e5f3d311d3080e6f0121063e9adc2490ef1a; C2 paths:443/0x/cls,:443/0x/ls; response headerX-Payload-B64; build-report request headerSec-V - SHA-256
1a21bad1df69b51efebfd2fae849ac27722b0dfac7b1cb459cf81d38b36f9705(fa-solid-900.woff2) - Build markers:
A8,A8-n*,9-10094,9-2078,8-1259,8-1586,8-1144
Organisations named here
- Binary-Mindz
- NullReceiver
Technologies named here
- VS Code
- file
- grep / git grep
- GitHub
- git clone --bare
- agimtula_server
- GitHub repository activity API
- diegovillariber-backend
- jq / strict JSON parsers
- rk-dodani-backend
- GulamRosul/Curved-navigation-bar
- javascript-obfuscator
- eslint.config.mjs
- prisma.config.ts
- package.json dependencies
- Node.js
- TypeScript
- Ethereum
Threat actors named here
- PolinRider
- DPRK
- Contagious Interview
- NullReceiver
Related in Threat Intel
Reverse Engineering the Six Stages of MacSync Stealer and RAT
AI-Coded Malware | Analyzing Vibe-Coded AD Enumeration | Huntress
