Threat intel
PolinRider keeps rewriting GitHub history to plant malware
A DPRK-linked campaign hides loaders in repo config files and fake fonts, backdates the commits and reinfects after cleanup. How to find it and clean up.
PolinRider, a malware campaign that researchers attribute to North Korea, is still planting loaders in public GitHub repositories, and new research published between September 26 and September 30, 2026 shows it has changed how it finds its servers and keeps coming back after owners clean up. Infected developer machines amend the last commit in each repository they can push to, keep the original author, message and date, and force-push it. The poisoned commit looks old and ordinary in the history.
Anyone who clones and builds an infected project runs the loader. The OpenSourceMalware (OSM) team counted 1,951 compromised public repositories belonging to 1,047 owners as of April 11, 2026. If your developers reuse public templates and starter kits, push from laptops with broad GitHub access, or work on code shared by outside contributors, this campaign is aimed at the way you work.
How it works
The infection starts on a developer's computer, through a malicious npm package, a VS Code extension or a fake coding test. From there the malware appends heavily obfuscated JavaScript to configuration files that build tools load on every run: postcss.config.mjs, tailwind.config.js, eslint.config.mjs, next.config.mjs and vite.config.js are the most common targets in OSM's data. The real configuration still works, and the extra code sits after the export default or module.exports statement, pushed far to the right by a long run of spaces so a quick look in an editor shows nothing wrong.
Propagation is handled by a Windows batch file, temp_auto_push.bat. It reads the last commit's metadata with git log, sets the system clock back to that commit's timestamp, runs git commit --amend --no-verify to fold the payload into it, restores the clock and pushes with git push -uf origin. The --no-verify flag skips any local pre-commit hooks that might have caught the change. Because the amended commit keeps the original author, message and timestamp, git log and the GitHub commit list show nothing new.
Two newer tricks make the payload harder to spot. Payloads have turned up in fake Font Awesome files such as public/fonts/fa-solid-400.woff2, which hold JavaScript instead of font data and are run from a modified eslint.config.mjs or a VS Code task. Other repositories carry a .vscode/tasks.json with "runOn": "folderOpen", a VS Code feature that runs a task when a folder is opened. VS Code only runs these in a trusted workspace and asks once per workspace by default, so a developer who clicks Allow on a fresh clone runs the loader without typing a command.
What attackers are doing
The loader has moved its command-and-control (C2) lookup onto Ethereum. OSM reports that a variant first seen on September 3, 2026, fa-solid-500.woff2, reads its server address from transactions sent by an Ethereum wallet, and SafeDep tied the same loader family on September 30 to wallet 0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891. That is the signal wallet behind the Ethereum C2 technique we covered in our post on Ethereum transfer C2, and both SafeDep and Ransom-ISAC list 23.27.20.187, 181.214.149.147 and 181.214.149.148 among the servers it has pointed to. Earlier variants used TRON, Aptos and BNB Smart Chain the same way. The final payload is described as a new BeaverTail build by OSM and as the DEV#POPPER remote access trojan with the OmniStealer credential stealer in other reporting; all of them go after browser credentials, crypto wallets, password managers and developer tokens.
Cleanup alone has not held. OSM followed one organisation whose three backend repositories were reinfected after the developers removed the payload in May and again in August, because contributors whose machines were still infected force-pushed it back. A third variant, fa-solid-900.woff2, appeared on September 25 with heavier obfuscation and deeper folder paths.
The same operators publish packages too. In July, researchers counted 108 malicious packages and extensions tied to PolinRider: 19 npm libraries, 10 Packagist packages, 61 Go modules and one Chrome extension. On September 9, an attacker who held the npm account for @dforge-core/dforge-mcp for 105 minutes published version 0.2.21 with a loader. CloudSEK traced that loader to 65 repositories and found a second payload family there that matches PolinRider.
What to do
- Search your code. Run
grep -rnE '.{1000,}' --include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts' .in every repository, on every branch, since the payload is rarely shorter than a thousand characters on one line. To cover branches without checking each one out, rungit grep -nE '.{1000,}' $(git for-each-ref --format='%(refname)' refs/remotes) -- '*.js' '*.mjs' '*.cjs' '*.ts'after agit fetch --all. Search for the stringstemp_auto_push.bat,_$_1e42,global['_V']andglobal['!'], which OSM lists as markers of known variants. In GitHub Code Search,filename:temp_auto_push.batinside your organisation is a quick first check. - Check font files by content. Run
file public/fonts/*.woff*or open them in a hex viewer. A real WOFF2 file starts with the byteswOF2; one that starts with readable JavaScript is a payload. - Read
.vscode/tasks.jsonin every repository and remove any task you did not write, especially one withrunOn: folderOpenthat callscurl,nodeorpython. Leavetask.allowAutomaticTasksatoffand click Disallow when VS Code asks about automatic tasks in a project you just cloned. - Look at pushes, not commits. On each repository's main page, click Activity to the right of the file list, open the All activity menu and choose Force pushes. The activity view records who pushed and when, which a backdated commit cannot fake. Any force push to a default branch that nobody can explain is a lead.
- If
@dforge-core/dforge-mcpappears in a lockfile, move to 0.2.22 or later and treat any machine that ran 0.2.21 as exposed.
If you find an infection, the developer machine that pushed it comes first. Reimage it, then rotate everything it held: GitHub and npm tokens, SSH keys, cloud keys and browser-saved passwords. Only then remove the payload from every branch and force-push the clean files, or the next push from that laptop puts it back. Block the C2 addresses above at the proxy and firewall, knowing the operators can replace them with one Ethereum transaction.
Stop the next force push
PolinRider depends on an infected machine pushing, with the developer's own credentials, straight to a branch that allows history rewrites. A GitHub ruleset on every default and release branch with Block force pushes and Require a pull request before merging turns the batch script's silent amend into a rejected push. Require signed commits helps most when signing keys live on a hardware token that needs a touch, because the batch script cannot press it. Tokens and SSH keys on developer laptops should be scoped to the repositories each person works on, so one infected machine cannot reach hundreds.
We review GitHub organisation settings, branch rules and developer workstation controls as part of safeguarding and hardening, and read build and config code for hidden loaders in code review. If you want help checking whether any of your repositories carry this payload, open the chat and Yaali, our AI agent, will pass your question to an engineer.
Sources: SafeDep, PolinRider Switches to Ethereum C2, OpenSourceMalware, PolinRider is A/B Testing its Way Past Your Detections, OpenSourceMalware PolinRider dossier, Daily CyberSecurity, Mallory, Wiz threat center, The Hacker News, Cyberpress, CloudSEK, GHAPPIER, GBHackers, Ransom-ISAC, VS Code tasks documentation, GitHub Docs, activity view.