PolinRider — North Korea-Linked Open-Source Supply-Chain Campaign
Executive Summary
PolinRider is a North Korea-linked open-source supply-chain campaign tracked by the Socket Threat Research Team and attributed to the broader Famous Chollima / Contagious Interview activity cluster. Active since December 2025 and still expanding into July 2026, Socket identified 162 malicious release artifacts across 108 packages and extensions spanning four ecosystems — npm, Packagist (PHP), Go modules (80 compromised modules), and one Chrome extension. The operators compromise legitimate maintainer accounts and GitHub repositories, then plant obfuscated JavaScript loaders and publish trojanized versions downstream.
The signature tradecraft is developer-workstation auto-execution. Malicious JS is appended into legitimate config files — vite.config.js, eslint.config.js, and other *config.js — so it runs the next time the project builds or lints, and injected .vscode/tasks.json entries with "runOn": "folderOpen" auto-execute a fake .woff2 font file via Node.js the moment a developer opens the folder in VS Code. The loader reaches out to public blockchain RPC infrastructure (TRON, Aptos, BNB Smart Chain) used as a dead-drop resolver, retrieves an encrypted second stage, XOR-decrypts it with embedded keys and executes it with eval(). Follow-on payloads are the DEV#POPPER RAT (command execution, socket.io-based C2) and the OmniStealer information stealer (credential, browser-data and crypto-wallet theft). Operators cover their tracks by rewriting git history via force pushes with anti-dated commits and hiding code with whitespace padding beyond the default screen width.
This is a distinct named campaign from the previously-packaged BeaverTail / Contagious Interview interview-lure activity, though it shares the same DPRK cluster and the "developer as patient-zero" objective. The blast radius is any developer endpoint, CI runner, or container build host that installed an affected package, cloned a poisoned repo, or opened one in VS Code.
Defender priority: The highest-fidelity catch is a node process executing a .woff2 font file, or VS Code (Code/code) spawning node/a shell on folder open. Pair with a dev-context process resolving a blockchain RPC domain and a secondary detached interpreter. Sweep every dev repo and build agent for the PolinRider artifacts (.vscode/tasks.json folderOpen tasks, fake .woff2 files, and the named repos/packages in §4/§10), and treat any hit as a credential-compromise event — rotate developer secrets and wallets.
Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry Fwd |
|---|---|---|---|
| 1 | Socket Threat Research | Primary attribution + technical anatomy: 162 artifacts / 108 packages across npm/Packagist/Go/Chrome; config-file + .vscode/tasks.json folderOpen loaders; fake .woff2 execution; blockchain-RPC dead-drops (TRON/Aptos/BNB); XOR+eval(); DEV#POPPER + OmniStealer; compromised accounts/repos | yes |
| 1 | The Hacker News | "North Korean Hackers Publish 108 Malicious Packages and Extensions in PolinRider Campaign" — campaign naming, counts, ecosystem scope | yes |
| 2 | SecurityWeek | Corroborates NK developer-targeting supply-chain framing and payload roles | partial |
| 2 | Developer-Tech / DevOps.com | Packagist-ecosystem expansion; maintainer-account-takeover mechanics | partial |
| 2 | Rescana | Active-exploitation framing across npm/Packagist/Go/Chrome | partial |
Hunter note: The abused blockchain RPC endpoints are legitimate public infrastructure (TRON/Aptos/BNB) used as dead-drop resolvers — they are NOT attacker-owned IOCs, so Q3 is a behavioral hunt with expected FPs on genuine web3 developers, not a blocklist. Atomic file hashes and stage-2 C2 IPs for DEV#POPPER / OmniStealer were not broadly published at pack time — behavioral detection is primary; the IOC CSV uses REPLACE_WITH placeholders for hashes so no fabricated indicator ships. The named repos/accounts (Xpos587, 7span/sevenspan) are sample compromised identities from Socket, not an exhaustive list — pivot on Socket's live tracker for the full package inventory.
Hunt Brief & Attack Chain
Hunt Hypotheses (fidelity-ordered)
- H1 (high): A
nodeprocess executes a.woff2file — a font asset should never be a Node.js entry point. low FP - H2 (medium): VS Code (
Code/code) spawnsnodeor a shell immediately on folder open — therunOn:folderOpentask trigger. med FP - H3 (medium): A dev-tool-context process (
node/Code/npm) resolves a public blockchain RPC domain (TRON/Aptos/BNB) — loader dead-drop fetch. med/high FP - H4 (medium): A
nodeprocess spawns a secondary interpreter/shell child — decoded stage-2 execution aftereval(). med FP - H5 (medium): A
node/editor-context process makes a long-lived outbound connection to a non-RFC1918 raw IP —socket.ioDEV#POPPER C2 / OmniStealer exfil. med FP - H6 (medium): PolinRider artifacts on disk — a
.vscode/tasks.jsonwith afolderOpentask, a fake.woff2, or the named compromised packages/repos. med FP - H7 (medium): A dev-context process reads a browser credential store or crypto-wallet directory — OmniStealer collection. med FP
Attack Chain
| Step | Behavior | Telemetry | Hunt Angle |
|---|---|---|---|
| 1 · Resource Development | Compromise maintainer accounts/repos; publish 108 trojanized packages across npm/Packagist/Go/Chrome | n/a (registry-side) | SCA / repo audit (H6) |
| 2 · Initial Access | Developer installs package / clones poisoned repo / opens it in VS Code | FileWrite / ProcessRollup2 | Artifact sweep (H6) |
| 3 · Execution | Loader in *config.js runs on build/lint; .vscode/tasks.json folderOpen runs fake .woff2 via node | ProcessRollup2 | node runs .woff2 (H1); Code spawns node (H2) |
| 4 · C2 / Ingress | Loader fetches encrypted stage-2 from blockchain RPC dead-drop (TRON/Aptos/BNB) | DnsRequest / NetworkConnectIP4 | RPC dead-drop (H3) |
| 5 · Defense Evasion | XOR-decrypt embedded payload; eval() execute; whitespace-padding obfuscation | ProcessRollup2 | node spawns child interpreter (H4) |
| 6 · Command & Control | DEV#POPPER RAT: command exec over socket.io C2 | NetworkConnectIP4 | node raw-IP egress (H5) |
| 7 · Collection / Credential Access | OmniStealer harvests credentials, browser data, crypto wallets | FileWrite / file access | Wallet/browser store read (H7) |
| 8 · Defense Evasion | Git history rewrite via force push with anti-dated commits | n/a (VCS-side) | Repo forensics / branch-protection audit |
Consolidated IOC Table
| Type | Value | Conf | Action | Context |
|---|---|---|---|---|
| GitHub account | Xpos587 | high | hunt | Compromised/operator identity (Socket) |
| Repo | Xpos587/git2md, Xpos587/markfetch | high | hunt | Poisoned repos with injected JS loaders |
| Repo | Artiffusion-Inc/mirofish | high | hunt | Poisoned repo (Socket) |
| Packagist / org | sevenspan namespace · GitHub org 7span · 7span/react-list | high | hunt | Compromised Packagist namespace / repo |
| File (loader host) | vite.config.js, eslint.config.js, other *config.js | medium | hunt | Obfuscated JS appended into legit config |
| File (auto-run) | .vscode/tasks.json with "runOn": "folderOpen" | high | hunt | Auto-executes payload on folder open |
| File (payload) | Fake .woff2 font file executed via Node.js | high | hunt | Font asset masquerading as JS entry point |
| Behavior | Loader → public blockchain RPC dead-drop (TRON, Aptos, BNB Smart Chain) | medium | hunt | Legit RPC infra abused as C2 resolver |
| Behavior | XOR-decrypt embedded stage-2 + eval() execution | medium | hunt | Second-stage deobfuscation/exec |
| Malware | DEV#POPPER (command exec, socket.io C2) | high | hunt | Stage-2 RAT |
| Malware | OmniStealer (credential/browser/wallet theft) | high | hunt | Stage-2 infostealer |
| SHA256 | REPLACE_WITH_SHA256 (loader / .woff2 / stage-2) | medium | detect | Populate when vendor hashes publish |
| IPv4 / domain | REPLACE_WITH_STAGE2_C2 | medium | detect | DEV#POPPER socket.io C2 — not yet public |
Affected Surface & Telemetry Matrix
| Surface | Required Telemetry | Priority | Gap Risk |
|---|---|---|---|
| Developer endpoints (mac/Win/Linux) | ProcessRollup2, DnsRequest, NetworkConnectIP4, file-write events | critical | Low — Falcon sensor coverage typical |
| CI/CD build agents | ProcessRollup2, NetworkConnectIP4 | critical | High — ephemeral runners may lack sensor |
| Container build hosts | ProcessRollup2 (container-aware), NetworkConnectIP4 | high | High — short-lived containers |
| Network egress | DnsRequest, NetworkConnectIP4, proxy logs | high | Medium — RPC dead-drop rides legit domains |
| Source repos / VCS | Branch-protection + force-push audit, SCA/SBOM | high | High — VCS-side, out-of-band tooling |
ATT&CK Mapping
| Tactic | Technique | Observed Behavior | Query / Control |
|---|---|---|---|
| Resource Development | T1195.002 — Supply Chain Compromise: Software Supply Chain | 108 trojanized packages across npm/Packagist/Go/Chrome | H6 / SCA + Q6 |
| Execution | T1059.007 — JavaScript | Loader in config files; fake .woff2 run via node | Q1 / Q2 |
| Execution | T1204.002 — User Execution: Malicious File | Developer opens poisoned repo / installs package | Q2 / Q6 |
| Command & Control | T1102 — Web Service (dead-drop resolver) | Stage-2 fetched from public blockchain RPC | Q3 |
| Command & Control | T1105 — Ingress Tool Transfer | Encrypted second-stage download | Q3 / Q5 |
| Defense Evasion | T1140 — Deobfuscate/Decode Files or Information | XOR-decrypt embedded payload; whitespace padding | Q4 |
| Defense Evasion | T1027 — Obfuscated Files or Information | eval() of decoded stage-2; hidden config JS | Q1 / Q4 |
| Command & Control | T1071.001 — Application Layer Protocol: Web | DEV#POPPER socket.io C2 | Q5 |
| Credential Access | T1555 — Credentials from Password Stores | OmniStealer browser credential theft | Q7 |
| Collection / Impact | T1005 / T1657 — Data from Local System / crypto theft | OmniStealer wallet + browser-data exfil | Q7 |
Native Audit-Log Hunts (non-CQL)
Repo / project sweep for PolinRider auto-run artifacts (dev endpoints + agents)
# VS Code folderOpen auto-run tasks (the auto-execution trigger)
grep -rln "folderOpen" --include=tasks.json .
# Fake font files that are actually JS entry points
find . -type f -name "*.woff2" -exec sh -c 'head -c 4 "$1" | grep -qv "wOF2" && echo "SUSPECT: $1"' _ {} \;
# Malicious JS appended into config files (look for eval / long whitespace-padded lines)
grep -rEln "eval\(|Buffer\.from\(.*base64|fromCharCode" --include=*config.js .
Named compromised packages / repos (SCA + git remotes)
# npm / Packagist / Go dependency trees npm ls 2>/dev/null | grep -Ei "git2md|markfetch|react-list" grep -rEln "Xpos587/(git2md|markfetch)|Artiffusion-Inc/mirofish|7span/react-list|sevenspan/" . # Go module cache grep -rEln "git2md|markfetch|mirofish" "$(go env GOPATH 2>/dev/null)/pkg/mod" 2>/dev/null
Blockchain RPC dead-drop egress from dev tooling (proxy/DNS logs)
# In your web-proxy / DNS log platform, look for build/editor hosts resolving: # *.trongrid.io *.aptoslabs.com bsc-dataseed* *.bnbchain.org # from a node/Code/npm process context outside your known web3 dev group.
Note: Because operators rewrite git history with anti-dated force pushes, a clean-looking commit log does NOT clear a repo — audit branch-protection/force-push events server-side (GitHub audit log) and pivot on the endpoint process/network telemetry (Q1–Q5).
CrowdStrike LogScale CQL Hunt Queries
Looks for: a node process whose command line references a .woff2 file — the PolinRider folderOpen payload. A font asset should never be a Node.js entry point, making this near-zero-FP.
// HUNT: node executing a .woff2 font file (PolinRider loader) // MITRE: T1059.007, T1027 // CONF: high FP: low COST: low | REQUIRES: ProcessRollup2 // FALSE POSITIVES: none expected — .woff2 is a font, not a JS entry point #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ImageFileName=/(\/|\\)node(\.exe)?$/i | CommandLine=/\.woff2(\b|["' ])/i | table([ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine, SHA256HashData, aid], limit=200)
Looks for: the VS Code editor (Code/code) spawning node or a shell — the runOn:folderOpen task trigger. FP: VS Code legitimately spawns node for extensions/build tasks; pair with Q1/Q3 on the same host and baseline expected task activity.
// HUNT: VS Code spawns node/shell on folder open (folderOpen task) // MITRE: T1059.007, T1204.002 // CONF: high FP: medium COST: low | REQUIRES: ProcessRollup2 // FALSE POSITIVES: legit VS Code tasks / extensions spawning node // TUNING: correlate with Q1 (.woff2) or Q3 (RPC) on the same aid; baseline dev hosts #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^Code( - Insiders)?(\.exe)?$/i | ImageFileName=/(\/|\\)(node|sh|bash|zsh|cmd|powershell|osascript)(\.exe)?$/i | table([ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine, aid], limit=200)
Looks for: a node/editor/npm-context process resolving a public blockchain RPC domain (TRON/Aptos/BNB) — the loader dead-drop fetch. FP: genuine web3 developers query these endpoints constantly — this is a behavioral hunt, not a blocklist. Scope to hosts outside your known blockchain/web3 dev group and correlate with Q1/Q2.
// HUNT: dev-context process resolving blockchain RPC dead-drop // MITRE: T1102, T1105 // CONF: medium FP: high COST: low | REQUIRES: DnsRequest // FALSE POSITIVES: legitimate web3/crypto developers — RPC infra is public/legit // TUNING: exclude known web3 dev hosts; require a node/Code/npm ContextBaseFileName #event_simpleName=DnsRequest | DomainName=/(trongrid\.io|aptoslabs\.com|bsc-dataseed|bnbchain\.org|binance\.org)$/i | ContextBaseFileName=/^(node|Code|npm|npx|go)(\.exe)?$/i | table([ComputerName, UserName, ContextBaseFileName, DomainName, aid], limit=200)
Looks for: a node process spawning a shell or a second interpreter — the decoded stage-2 executing after eval(). FP: build toolchains legitimately shell out; require a suspicious ancestor (editor/package manager) and correlate with Q1–Q3.
// HUNT: node spawns secondary shell/interpreter (decoded stage-2) // MITRE: T1059.007, T1140 // CONF: medium FP: medium COST: medium | REQUIRES: ProcessRollup2 // FALSE POSITIVES: build tools legitimately shelling out (webpack/postinstall) // TUNING: correlate on aid with Q1/Q2/Q3; scope to editor/package-manager ancestry #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^node(\.exe)?$/i | ImageFileName=/(\/|\\)(sh|bash|zsh|cmd|powershell|osascript|curl|wget|node)(\.exe)?$/i | table([ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine, aid], limit=200)
Looks for: a node-context process connecting outbound to a non-RFC1918 raw IP — generalizes the DEV#POPPER socket.io C2 / OmniStealer exfil beyond named IOCs. FP: node apps that talk to IP-based SaaS; exclude known egress ranges and focus on install/edit windows.
// HUNT: node/editor outbound to raw external IP (DEV#POPPER C2 / exfil) // MITRE: T1071.001, T1105 // CONF: medium FP: medium COST: medium | REQUIRES: NetworkConnectIP4 // FALSE POSITIVES: node apps beaconing to legitimate IP-based services // TUNING: exclude RFC1918 + known SaaS ranges; correlate with Q1-Q4 on aid #event_simpleName=NetworkConnectIP4 | ContextBaseFileName=/^(node|Code)( - Insiders)?(\.exe)?$/i | RemoteAddressIP4!=/^(10\.|127\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)/ | table([ComputerName, UserName, ContextBaseFileName, RemoteAddressIP4, RemotePort, aid], limit=200)
Looks for: file-system evidence of the loader dropzone — a .vscode/tasks.json or a .woff2 written into a project tree. FP: legitimate projects ship both; treat as a triage lead, then inspect the file for a folderOpen task / non-font content. ⚠ validate in tenant — non-PE file-write telemetry (FileCreateInfo) is not populated in every environment; confirm the event flows before relying on this query.
// HUNT: PolinRider on-disk artifacts (folderOpen task / fake .woff2) // MITRE: T1195.002, T1204.002 // CONF: medium FP: medium COST: medium | REQUIRES: FileCreateInfo (validate in tenant) // FALSE POSITIVES: legit .vscode/tasks.json and .woff2 assets — inspect content // TUNING: prioritize tasks.json written by node/Code/git; pair with §7 grep sweep #event_simpleName=/FileCreateInfo|NewScriptWritten/ | TargetFileName=/\.vscode[\/\\]tasks\.json$/i OR TargetFileName=/\.woff2$/i | table([ComputerName, UserName, TargetFileName, ContextBaseFileName, aid], limit=200)
Looks for: a node-context process touching a browser credential store or crypto-wallet directory — OmniStealer collection. FP: password managers / legit wallet apps access these paths; require a node/editor context, not the browser itself. ⚠ validate in tenant — file-read/write visibility for these paths varies; confirm telemetry.
// HUNT: node-context process reading browser/wallet credential stores // MITRE: T1555, T1005 // CONF: medium FP: medium COST: medium | REQUIRES: FileCreateInfo/read (validate in tenant) // FALSE POSITIVES: password managers / legit wallet apps — require node context // TUNING: exclude the browser's own process; alert when ContextBaseFileName is node #event_simpleName=/FileCreateInfo|NewScriptWritten/ | TargetFileName=/(Login Data|Local State|key3\.db|key4\.db|logins\.json)$/i OR TargetFileName=/[\/\\](Ethereum|Electrum|Exodus|Ledger Live|MetaMask)[\/\\]/i | ContextBaseFileName=/^(node|Code)( - Insiders)?(\.exe)?$/i | table([ComputerName, UserName, ContextBaseFileName, TargetFileName, aid], limit=200)
CrowdStrike Custom IOA Recommendations
| IOA Name | Pattern | Action | Exclusions |
|---|---|---|---|
| node-executes-woff2 | Process node, CommandLine contains .woff2 | Detect (Critical) → Prevent after tuning | None expected |
| vscode-folderopen-spawn | Parent Code/code spawning node/shell | Detect (High) | Baselined dev hosts with known build tasks |
| node-child-interpreter | Parent node spawning shell/second interpreter | Detect (Medium) | Known build toolchains that shell out |
| node-rpc-deaddrop | node/Code resolving TRON/Aptos/BNB RPC domain | Detect (Medium) — Investigate only | Known web3/crypto dev group |
Promotion path: Q1 is a strong IOA/Prevent candidate (near-zero FP, attacker-specific). Q2 is a good Detect after per-environment baselining. Q3–Q7 remain Investigate-only hunt queries until FP-baselined, and are strongest when correlated on the same aid.
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags sha256,REPLACE_WITH_LOADER_SHA256,prevent,critical,2027-01-09,PolinRider JS loader / fake .woff2,campaign:PolinRider sha256,REPLACE_WITH_STAGE2_SHA256,prevent,critical,2027-01-09,DEV#POPPER / OmniStealer stage-2,campaign:PolinRider domain,REPLACE_WITH_STAGE2_C2,detect,high,2026-10-09,DEV#POPPER socket.io C2 (populate when public),campaign:PolinRider ipv4,REPLACE_WITH_STAGE2_C2_IP,detect,high,2026-10-09,stage-2 C2 IP (populate when public),campaign:PolinRider
woff2_exec := node <something>.woff2 (font run as JS entry point) folderopen := Code -> node|sh .vscode/tasks.json "runOn":"folderOpen" deaddrop := node|Code -> DNS *.trongrid.io|*.aptoslabs.com|bsc-dataseed* stage2_exec := node -> sh|bash|node (eval() of XOR-decoded payload) c2 := node|Code -> outbound raw non-RFC1918 IP (socket.io) collection := node -> read Login Data|MetaMask|Exodus|Ledger Live
# Sample compromised repos/accounts (Socket — not exhaustive) Xpos587/git2md Xpos587/markfetch Artiffusion-Inc/mirofish 7span/react-list (GitHub org: 7span) Packagist namespace: sevenspan Operator/compromised account: Xpos587 # Full live inventory: socket.dev/supply-chain-attacks/polinrider
# 1. Sweep repos/agents for .vscode/tasks.json "folderOpen" tasks + fake .woff2 # 2. grep *config.js for appended eval()/base64 loaders (whitespace-padded) # 3. Set VS Code: "security.workspace.trust.enabled": true (no auto task run) # 4. Enforce --ignore-scripts in CI; disable auto-run tasks on untrusted repos # 5. Rotate ALL developer secrets reachable from affected hosts: # git/npm tokens, cloud keys, SSH keys, browser-stored creds, crypto wallets # 6. Reimage any endpoint that opened a poisoned repo / ran an affected install # 7. Audit GitHub org for force-push / anti-dated-commit events; enforce protection
Detection Validation Gates
| Gate | Check | Pass Criteria |
|---|---|---|
| Telemetry ready | ProcessRollup2, DnsRequest, NetworkConnectIP4 flowing from dev + CI hosts; confirm file-write events for Q6/Q7 | Event types present in last 24h |
| Benign baseline | Run Q2 & Q3 over 7d; catalog legit VS Code node-spawns and web3-dev RPC egress | Known-good list built; exclusions added |
| Positive test | In a sandbox, run node ./x.woff2 and a VS Code folderOpen task spawning node | Q1 & Q2 fire on the test artifacts |
| Correlation | Confirm Q1–Q5 can be joined on aid to build a single-host chain | Multi-signal host list produced |
| Promotion | Q1 FP-clean for 7d; Q2 baselined | Promote Q1 to Custom IOA / Prevent |
Hardening — Tiered
- Enable VS Code Workspace Trust: ensure
security.workspace.trust.enabled = true(default) and never "Trust" an unknown repo — untrusted folders do not auto-runfolderOpentasks. (M1042 Disable or Remove Feature) - Disable install scripts in CI:
npm ci --ignore-scripts/pnpm config set enable-pre-post-scripts falseon build agents. (M1042) - Sweep + remove artifacts: delete PolinRider
folderOpentasks, fake.woff2and named packages/repos (§7/§10). (M1051 Update Software) - Rotate developer secrets reachable from any affected host: git/npm tokens, cloud/SSH keys, browser creds, crypto wallets. (M1027 Password Policies / M1032 MFA)
- Egress control for dev/CI: allowlist package registries + known mirrors; alert on build/editor processes reaching blockchain RPC or raw external IPs. (M1037 Filter Network Traffic)
- VCS branch protection: block force-push to protected branches, require signed commits + reviews, and monitor the GitHub org audit log for anti-dated-commit / history-rewrite events. (M1018 User Account Management)
- SCA in CI: fail builds on newly-published / low-reputation dependency versions; quarantine packages under a cooldown. (M1016 Vulnerability Scanning)
- Private registry / pull-through proxy (Verdaccio, Artifactory, GitHub Packages) with quarantine on newly-published versions. (M1016)
- Ephemeral, network-segmented build runners with no standing credentials and per-job secret injection — contains developer-workstation compromise. (M1030 Network Segmentation)
- Developer endpoint segmentation: isolate build/dev hosts from crown-jewel data; enforce hardware-backed keys so stolen browser/wallet material is non-replayable. (M1032 MFA / M1030)
Deployable Playbooks
1 · Enforce VS Code Workspace Trust + disable auto-tasks (settings.json)
// User settings.json — block untrusted-folder auto execution
{
"security.workspace.trust.enabled": true,
"security.workspace.trust.untrustedFiles": "prompt",
"task.allowAutomaticTasks": "off"
}
// Fleet-manage via MDM / GPO by deploying this to each user's settings.json
2 · Sweep & flag PolinRider artifacts (bash)
#!/usr/bin/env bash
set -euo pipefail
ROOT="${1:-$HOME}"
echo "[*] folderOpen auto-run tasks:"
grep -rln "folderOpen" --include=tasks.json "$ROOT" || true
echo "[*] .woff2 files that are NOT real fonts (bad magic):"
find "$ROOT" -type f -name "*.woff2" -exec sh -c \
'head -c 4 "$1" | grep -qv "wOF2" && echo " SUSPECT: $1"' _ {} \; 2>/dev/null || true
echo "[*] eval/base64 loaders appended into config files:"
grep -rEln "eval\(|Buffer\.from\([^)]*base64|fromCharCode" --include=*config.js "$ROOT" || true
echo "[*] named compromised repos/packages:"
grep -rEln "Xpos587/(git2md|markfetch)|Artiffusion-Inc/mirofish|7span/react-list|sevenspan/" "$ROOT" || true
3 · CI hardening (pipeline YAML fragment)
# Never run lifecycle scripts on untrusted installs npm ci --ignore-scripts pnpm config set enable-pre-post-scripts false yarn config set enableScripts false # Deny build-agent egress except to the registry/mirror allowlist
4 · Falcon RTR triage one-liner (macOS/Linux dev host)
find "$HOME" -maxdepth 6 -path "*/.vscode/tasks.json" -exec grep -l "folderOpen" {} \; 2>/dev/null
# Windows (PowerShell)
Get-ChildItem $env:USERPROFILE -Recurse -Filter tasks.json -EA SilentlyContinue |
Select-String -Pattern 'folderOpen' | Select-Object Path
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Network-contain (Falcon RTR contain) any host with Q1/Q2 hits or a confirmed folderOpen task; freeze affected CI runners | SOC / IR | aid list, contain timestamps |
| Eradicate | Kill stage-2 node/child process; delete fake .woff2, malicious tasks.json, and appended config-file JS; remove named packages/repos | IR / DevOps | Process tree, file list |
| Eradicate | Rotate ALL secrets reachable from affected hosts (git/npm/cloud/SSH tokens, browser creds, crypto wallets) — assume OmniStealer exfil | IR / Platform | Rotation tickets, wallet moves |
| Recover | Reimage endpoints that opened a poisoned repo / ran an affected install; restore from known-good images | DevOps / IT | Reimage records |
| Recover | Re-enable dev/CI only after Workspace Trust + ignore-scripts + egress control enforced; monitor Q3/Q5 for re-beacon | SOC | 7-day clean window |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1059.007 / T1027 | node executes .woff2 | Q1 | Yes | Good |
| T1204.002 | VS Code folderOpen spawn | Q2 | Yes | Good |
| T1102 / T1105 | blockchain RPC dead-drop | Q3 | No | Partial — high FP, hunt only |
| T1059.007 / T1140 | stage-2 interpreter spawn | Q4 | Yes | Partial |
| T1071.001 / T1105 | socket.io C2 / exfil | Q5 | No | Partial |
| T1195.002 / T1204.002 | on-disk loader artifacts | Q6 | No | Partial — file-write telemetry varies |
| T1555 / T1005 | browser/wallet theft | Q7 | No | Partial — file-access telemetry varies |
| Registry-side publish | Account/repo takeover | — | No | GAP — SCA/VCS audit only |
| Stage-2 atomic IOCs | DEV#POPPER / OmniStealer hashes + C2 | — | No | GAP — not yet public |
Behavioral coverage of the auto-run→fetch→exec chain is strong at the front (Q1/Q2) and correlative in the middle (Q3–Q5). Gaps are registry/VCS-side takeover (needs SCA/SBOM + GitHub audit) and stage-2 atomic indicators (await published hashes/C2 — see CSV placeholders). Validate §11 gates before promoting Q1 to Prevent.
Hunt Summary Ticket
TITLE: PolinRider (DPRK supply-chain) — dev-workstation auto-run → RAT/stealer hunt
SEVERITY: Critical (active DPRK supply-chain; 108 packages, 4 ecosystems)
SCOPE: Developer endpoints, CI/CD build agents, container build hosts
HYPOTHESIS: A poisoned package/repo auto-runs a JS loader (config file /
.vscode folderOpen / fake .woff2) that fetches an XOR+eval stage-2
(DEV#POPPER + OmniStealer) via blockchain RPC dead-drops.
QUERIES: Q1 node runs .woff2 | Q2 Code folderOpen spawn | Q3 RPC dead-drop
Q4 node child interpreter | Q5 node raw-IP C2 | Q6 on-disk artifacts
Q7 browser/wallet theft
DO FIRST: 1) Run Q1/Q2 fleet-wide; contain any hits
2) Sweep repos/agents for folderOpen tasks + fake .woff2 (§7)
3) Enforce VS Code Workspace Trust + --ignore-scripts in CI
FINDINGS: <fill: hosts, aids, packages/repos, secrets/wallets exposed>
GAPS: Registry/VCS-side takeover; stage-2 atomic hashes + C2 pending
ACTIONS: Rotate developer secrets + wallets; reimage affected hosts;
audit GitHub org for force-push / anti-dated commits
OWNER: <assign SOC/IR lead>
VERSION: v0.2 · 2026-07-09 · HuntPack
Changelog
table() calls now carry an explicit row limit: the default is 200 and truncation is silent, so a capped result was indistinguishable from a complete one. Atomic-IOC sweeps (filters over 5+ hashes or C2 IPs) use limit=max so a wide infection is never silently under-scoped; behavioural hunts use limit=200, where exceeding the cap indicates the query needs tuning. Where present, event names that do not exist in the Falcon data model were corrected (e.g. ServiceInstalled is a Sysmon concept, not a Falcon event; ElfFileWritten is ELFFileWritten) — such queries could never return a row. No detection logic, fields, or IOCs changed.References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | Socket — PolinRider: North Korea-Linked Supply Chain Campaign Expands | Primary attribution, anatomy, IOCs, payloads | 2026-07-09 |
| 1 | The Hacker News — NK Publish 108 Malicious Packages (PolinRider) | Naming, counts, ecosystem scope | 2026-07-09 |
| 2 | SecurityWeek — NK Target Open Source Developers | Corroboration of framing + payload roles | 2026-07-09 |
| 2 | Developer-Tech — PolinRider expands to Packagist | Packagist expansion, takeover mechanics | 2026-07-09 |
| 2 | Socket — PolinRider live tracker | Full live package/artifact inventory | 2026-07-09 |