RePointing: 17,610 Fake GitHub Repos That Were Never Deleted, Only Re-Aimed

Summary
17,610 fake GitHub repositories are serving SmartLoader, and Apiiro verified a malicious ZIP behind every single one. That is nearly double the largest list published before.
What matters is not the scale but how the campaign renews itself. Around 06:30 UTC on 04/10/2026, a script began editing READMEs at scale. Over the next 34 hours, more than 13,000 repositories were re-pushed, peaking at 2,999 an hour. In the sampled commits, 97% touched only the README and 88% pointed the Download button at a ZIP that installs SmartLoader.
Not one new repository was created. The fleet was already there; it just got re-aimed. Apiiro calls the technique RePointing.
Priority actions:
- Treat any infostealer infection on a developer machine as a compromise of your organization's GitHub account: revoke sessions and tokens immediately, not just rotate the password.
- When hunting, match both of GitHub's URL forms. The
github.com/<owner>/<repo>/raw/...zipform is what most blocklists are missing.
RePointing: Keep the Repo, Move the Link
The idea is simple: keep a repository that has existed for months, carries a real project's name and has real history, and change only where its Download button points.
Such a repository carries trust that a fresh fork does not. Keeping the repo and swapping only the link preserves that trust, at a cost of one commit.
Apiiro tested whether this was a search-ranking trick. Across 120 GitHub searches for lure names, re-pushed lures ranked no better than lures left alone. The conclusion: not SEO, simply cheaper. Most of the live fleet stays up, so there is little to rebuild.
The waves are not random either. The first two are the same program run twice, with near-identical minute-by-minute profiles, each walking the lures in the order they were first armed (rank correlation 0.96), at about 1.6 pushes a second, each from a different account. This is a pool of workers working through one sorted inventory.
One more detail: they push exactly one lure per account. Of the 1,101 accounts holding two or more lures, all but 2 had at most one re-pushed. The older lures on the same account stay up, untouched and still downloadable.
The Attack Chain
The lure. A repo takes a real project's name, the README is rewritten into a friendly install guide, and a Download badge points at a ZIP sitting in the repo's own tree.
The most telling detail in that guide: step 3 reads "Windows may show a security screen. If it appears, click More info, then click Run anyway." The instructions teach the victim to click through SmartScreen.
Where the ZIPs hide. In src/, assets/, docs/, and in about 375 repos, the folders reviewers are trained to skip entirely: node_modules/, .github/, __pycache__/, dist/, vendor/. Another 5,474 lures hide the ZIP in a top-level folder named with an obscure English word (ophthalmoscopic/, Arverni/); 99.6% of those words appear letter for letter in the 1934 Webster's word list shipped with macOS and BSD at /usr/share/dict/words.
Inside the ZIP are three or four files, renamed per repo but always with the same roles:
| File | Looks like | Actually is |
|---|---|---|
Launcher.cmd / App.bat |
a setup script | one line: start the runtime, hand it the "text file" |
luajit.exe / luau.exe / gcc.exe |
a tool binary | a LuaJIT interpreter, renamed |
lua51.dll |
a dependency | the interpreter's library, often identical across repos |
userdata.txt / ico64.txt / tcp.log |
an icon, a log, a licence | a ~300 KB obfuscated Lua program squeezed onto one line |
After the double-click. The launcher hides its window, profiles the machine (including a lookup to ip-api.com), reads its current C2 address from a Polygon smart contract, and pulls the next stage from other GitHub repos. The chain ends with StealC injected into a legitimate-looking process.
StealC takes saved passwords, but the part that matters for developers is live sessions: a stolen cookie for GitHub, a cloud console or an SSO portal skips both the login and the MFA prompt.
The Gap Between "Listed" and "Removed"
This is the most operationally valuable part of the report.
GitHub serves the same file from two URL forms, and blocklist coverage is wildly uneven:
| README link form | Live repos | On URLhaus |
|---|---|---|
raw.githubusercontent.com/<owner>/<repo>/…zip |
9,455 | 4,807 (51%) |
github.com/<owner>/<repo>/raw/…zip |
7,939 | 121 (2%) |
The reason: collection pipelines look for raw.githubusercontent.com, because that is where raw files are served. The github.com/.../raw/ path redirects there but does not match the pattern. Apiiro's own first sweep made the same mistake; fixing one regex surfaced thousands more lures in data they had already downloaded.
Being on a blocklist is not being taken down. In the URLhaus-listed part of the fleet, 97% of the October re-pushes pointed the README at a ZIP URLhaus had listed back in June, and 99.96% of listed ZIPs were still downloadable.
And a DNS blocklist cannot help here. You cannot block one file on github.com without blocking GitHub. What remains is URL- and file-level checking, which only knows what has been listed: in one sample, all 38 URLhaus-listed URLs were flagged by at least one VirusTotal engine, while VirusTotal had seen only 6 of 38 unlisted github.com/.../raw/ URLs.
Deleting the linked file is not enough either, because there is always a spare:
- 444 forks host their own verified copy of the kit
- 913 repos still hold an older, unlisted kit ZIP next to the one the README currently links
- 322 release assets across 199 repos, already listed on URLhaus as SmartLoader, are all still downloadable, some since March 2025
- Issue attachments carrying the kit stay downloadable even where no issue mentions them
- 841 lures link a ZIP stored in a different repo, mostly the owner's own
<user>.github.io
RePointing works because there is always something to point at.
Which Lists Got Removed, and Which Did Not
Apiiro cross-checked three previously published lists:
| List | Published | Status today |
|---|---|---|
| Orchid, 9,330 repos | 06/2026 | 99.3% gone, almost entirely as banned accounts |
| Island, 7,600 repos | 07/2026 | 85% still up after 77 days |
| Hexastrike, 109 repos | 04/2026 | about half still up after 24 weeks |
Repos appearing on both the Island and Orchid lists are 71% gone, against 14% for the rest. The conclusion is blunt: what gets listed gets removed, and what is never listed stays up.
But removal barely touched what runs now: only 49 repos from Orchid's list are in today's fleet.
There are signs the operator watches disclosures. Orchid published on 18/06. The last mass build day before that was 17/06, one day earlier, and the next came 64 days later, against a usual gap of three. New accounts kept being registered through the pause and were armed in September.
Compromised Accounts and Bystander Victims
15,225 accounts hold 17,624 repos, so banning an account removes little more than one lure.
93% look like throwaways: 9,197 created since July 2024, and 5,022 older accounts with nothing real on them before the lure arrived. There are signs of batch farming: 750 accounts registered on 09/04/2026 alone, left to age before getting a lure.
But about 700 accounts belong to real developers. Apiiro flagged 965 as possible takeovers, then tested them against evidence that cannot be backdated: pull requests merged by other people, write access granted to other people's repos, comments, forks and issues by others, all with GitHub's own timestamps. 219 accounts have that kind of history, usually years of it, against 4 of 200 throwaway accounts. A hand check of 180 samples found roughly two-thirds to be real people, putting the total near 700.
The switch shows in the commits. None of the 951 lure commits checked is signed or made in GitHub's web editor, although 348 of 490 owners with real history had signed their earlier work. And they were made centrally, on one schedule: most ZIPs landed on 19–23/02/2026, 1,741 commits across 783 accounts, a median 23 seconds apart, with not one between midnight and 5 a.m. UTC, even on accounts where that is mid-morning.
The operator still has that access. Accounts taken over in February were pushed again in October: since 04/10, 40% of lures on established developers' accounts and 51% on the history-flagged ones were re-pushed. Seven months on, the owners show no public sign of noticing; only 2 ever deleted the ZIP, and Apiiro found no "I was hacked" posts.
And that access was used again. Between 01/02 and 19/03, 139 of these accounts pushed kit ZIPs into 270 repositories they do not own: classmates' projects, team capstones and course repos under university and bootcamp organizations. 313 of 314 planted files are still downloadable, hidden in a capstone's node_modules/, a team's .github/workflows/, or a Java build's target/classes/.
The owners of those repos did nothing wrong. They gave a teammate write access, and the teammate's account was turned against them.
Automation carries it further: one lure posing as a router add-on was picked up by the automated syncs of two OpenWrt community package feeds in late September, ZIP included.
The AI Angle: From AgentBaiting to Model-Written READMEs
The lures target the AI ecosystem directly. 4,551 lures (26%) share their exact name with a real project that has at least 50 stars, and 46% of those copy AI, MCP, Claude or agent tooling: mattpocock/skills has 26 lures, anthropics/claude-code has 12. They borrow trust through names, not stars, since almost all have few or none. Often the code comes too: in a random sample of 82 name-matched lures, 30 (37%) carry at least 90% of the real project's files byte for byte.
1,169 lures have live listings on AI skill directories, led by awesomeskills.dev (1,052) and skills.lc (169), each with a copy-paste install command.
That is the sharp edge. In 167 lures, the kit ZIP sits in the same folder as a SKILL.md that never mentions it, and 96 of those are listed on a directory. npx skills add clones the repo and copies that whole folder, ZIP included, into the agent's skills directory, and nothing about the skill itself looks wrong.
Two things to keep straight so this is not overstated: the lures do not rely on this route, since their READMEs link the ZIP directly; and landing on disk is not execution, because nothing is extracted or started until someone, or some agent, opens the ZIP and runs the launcher. But it puts the malware one step closer to the machine holding your keys.
Island calls this route AgentBaiting, and in their tests ChatGPT, Gemini and Claude all surfaced malicious repositories when prompted with related tasks, sometimes relaying the installation instructions. In controlled testing, Claude Code cloned a malicious repository and downloaded the files to a test machine, then detected suspicious indicators and stopped before execution.
The READMEs are model-written, and nobody reads them before they ship. The writing style itself was flagged by Trend Micro back in 2025, so that is not new. What is new is what the text gives away about how it was made:
- 17 READMEs begin with a stray markdown code fence, the wrapper a chat model puts around its answer, pasted in whole
- 13 keep the model's own note to the user, such as "This README paraphrases and expands upon the provided information"
- 39 tell people to run an Android, iOS or Linux project on "Windows 10 or Windows 11"
- Of 157 human-written READMEs from the impersonated projects used as a control, none has any of these
Each one is generated fresh: among 2,196 READMEs, Apiiro found 2,116 distinct section layouts and only four exact duplicates.
Apiiro's conclusion is worth quoting in spirit: this is not an AI agent running the campaign; it is a pipeline that calls a model once per repo and publishes whatever comes back, with nobody proofreading.
The C2 Lives on a Blockchain
How do seventeen thousand ZIPs keep working when C2 servers keep getting burned? SmartLoader does not hardcode one — it asks a smart contract on Polygon where to go next.
That lets the operator move C2 without touching a single repo. But every move is a public, permanent transaction.
Reading the contract's history gives the operator's full C2 changelog without sending a packet to their infrastructure. Apiiro counted 21 updates between 11/2025 and 09/2026, roughly one every two weeks, including two addresses absent from any public reporting they could find. They also note a block explorer was missing 5 of the wallet's 24 transactions, so count it yourself.
A few points lined up against fleet activity:
| Date | Contract update | What else was happening |
|---|---|---|
| 16/11/2025 | 93.123.39[.]74, the first address |
|
| 08/02/2026 | 89.169.12[.]160 |
11 days before the 19–23/02 account takeovers |
| 13/07/2026 | 83.97.20[.]150 |
the last address in public reporting |
| 11/08/2026 | 194.48.248[.]94 |
never publicly reported |
| 17/09/2026 | 185.10.68[.]110 |
current, 2.5 weeks before the October waves |
Apiiro's recommendation here is worth adopting: watch this contract, because a new updateData transaction means a new C2 address to block. It is the cheapest IOC feed this campaign offers.
Analysis
This campaign is durable because it exploits a process gap, not a technical flaw. There is no CVE here. What is being exploited is that blocklists and code hosts are not wired together: a listed URL stays live, and listing does not stop the operator from pointing the same button at the same file again. No control on the user's side reaches that root cause.
Disclosure has measurable impact, but only on the listed portion. Orchid published and their list is 99.3% gone — yet only 49 of those repos are in today's fleet. That is a lesson in how to measure TI effectiveness: "our list is 99% removed" and "the threat is 99% reduced" are entirely different statements.
The kit is evolving faster than detection. The late-July generation uses a new obfuscator and drops the DLL entirely, cutting detections from 26–33 engines down to 2–9 of about 75 on VirusTotal. Meanwhile how it hides on GitHub did not change at all. Detection built on ZIP structure and file placement will outlast detection built on payload hashes.
Compromised developer accounts are the most serious and least discussed part. About 700 real accounts, 219 proven one by one, and after seven months nobody has noticed. Holding access that long suggests a long-lived credential such as a token or key, rather than a single browser session. For an organization, the risk is not that an employee downloads the wrong ZIP — it is that their GitHub account may already have been someone else's for months.
The real size is probably larger than the published figure. Two independent estimates, one against URLhaus and one against Island's list, both land around 21,000–22,000 repos. Apiiro is explicit that these rest on assumptions that do not fully hold, so the hard number remains the 17,610 they verified.
Relevance to Vietnam
- Vietnamese development teams install AI and MCP tooling from README and directory instructions. That is exactly the path AgentBaiting targets. One
npx skills addrun from a copy-paste snippet is enough to pull the ZIP onto the machine. - Course and group-project repos are real bystander victims in this report. The university and bootcamp model, with a shared GitHub organization and students holding write access across each other's repos, is common in Vietnam. One compromised student account can carry into dozens of others.
- A developer machine with an infostealer is an account incident, not an endpoint incident. StealC takes session cookies, so wiping the machine and changing the password is not enough. Sessions and tokens must be revoked and issued keys reviewed.
- The fake repos carry the correct project name and often the real code. "Right name, familiar code" is no longer any kind of trust signal.
Recommendations
For developers and AI-agent users:
- Never run a
.cmd,.bator.exefrom a ZIP linked in a README, even from a repo with exactly the right name. - Install skills and MCP servers from the official registry or the vendor's own repository, not from a copy-paste command on a third-party directory.
- Treat any infostealer infection as a GitHub compromise: revoke sessions and tokens, review issued keys, and move to passkeys.
For security teams:
- Hunt by file structure, not by hash: look for an archive containing a batch file, an executable containing the string
LuaJIT, alua51.dll, and one large single-line text file. This is the most durable signal because it has not changed across kit generations. - Block both URL forms at the proxy, and write hunting rules that match
raw.githubusercontent.com/<owner>/<repo>/…zipas well asgithub.com/<owner>/<repo>/(raw|blob)/…zip. The second form is most of what is missing. - Flag ZIPs committed into
node_modules/,.github/,dist/,vendor/,__pycache__/and build output folders in internal repos. There is no legitimate reason for a ZIP to be there. - Watch the Polygon contract for new C2 addresses, roughly every two weeks, without ever touching the attacker's infrastructure.
IOCs
Per Apiiro, figures frozen at 06/10/2026. Apiiro states that full repository, URL and hash lists are available to defenders on request.
SmartLoader payload clusters (SHA-256 of the obfuscated Lua payload)
8cede35b80b1deaf732c2b178d908f91b3e7a0c114d06dfae9075b8a9bf78b8f # 1,145 repos, 33 VT engines
f9436ccb986760ca379d6cd2f00726e032a1d9c250a9bd261d40d98b914e7ef9 # 795 repos, 34 VT engines
73f3c4ae76c086f962a06d790be6cc104a47c9d1bfcaebd12eebb3455b090660 # 511 repos, 8 VT engines
Apiiro hashed 120 distinct payloads, of which 106 are flagged on VirusTotal; the newest score only 2 to 9 of about 75 engines.
C2 dead-drop on Polygon
Contract: 0x1823A9a0Ec8e0C25dD957D0841e3D41a4474bAdc (created 15/11/2025)
Newest C2s:
194.48.248[.]94 (11/08/2026)
185.10.68[.]110 (17/09/2026, current)
Earlier, publicly reported:
93.123.39[.]74 (16/11/2025)
89.169.12[.]160 (08/02/2026)
83.97.20[.]150 (13/07/2026)
Apiiro notes they read from the chain only and never contacted these addresses.
Other malware behind the same style of fake install guide
18750cfa651271dc086f6806ac01c3c9d621e949b6127ff199a174cb774d8064 # Release.exe, nexe loader, 7 repos
33bba47346c03968977688bddbdd245210c06fb7686b4dfc78789c70e2a95219 # kidkadi.node, in-memory PE mapping
77e46759791c9230afc29baeb6cb048a9a05196db1a48705dd94aa2e74b93e0e # libcurl.dll, sideloaded by signed WinGup
Both URL forms to match when hunting
raw.githubusercontent.com/<owner>/<repo>/…zip # 51% URLhaus coverage
github.com/<owner>/<repo>/(raw|blob)/…zip # 2% coverage <- the gap
ZIP structure to look for
*.cmd or *.bat # one line, starts the runtime
*.exe # contains the string "LuaJIT", renamed to luajit/luau/gcc
lua51.dll # often identical across repos
*.txt or *.log # ~300 KB, obfuscated Lua, on a single line
Folders commonly used to hide the ZIP
node_modules/ .github/ __pycache__/ dist/ vendor/
src/ assets/ docs/
<an obscure English word>/ # from /usr/share/dict/words (Webster's 1934)
Other fingerprints
ZIP names: Software-v<1.0 to 3.9>.zip (never 4.x, never a patch number)
Commits: unsigned, default message "Update README.md", +0000 timezone
Account email: <first><last><four letters>@hotmail.com or @outlook.com
Network lookup: ip-api.com (the launcher's profiling step)
References
- Apiiro — Never Deleted, Only Re-Pointed: Inside the 17,600-Repo FakeGit Fleet That Re-Arms Overnight (10/2026): https://apiiro.com/blog/never-deleted-only-re-pointed
- BleepingComputer (Bill Toulas) — FakeGit malware campaign returns with 17,610 malicious GitHub repos (08/10/2026): https://www.bleepingcomputer.com/news/security/fakegit-malware-campaign-returns-with-17-610-malicious-github-repos/
- BleepingComputer (Bill Toulas) — FakeGit campaign uses 7,600 GitHub repos to push SmartLoader malware (21/07/2026): https://www.bleepingcomputer.com/news/security/fakegit-campaign-uses-7-600-github-repos-to-push-smartloader-malware/
- Island — 7,600-repo report and the AgentBaiting technique (07/2026)
- Orchid — 9,330-repo list and the README-only re-push analysis (06/2026)
- Hexastrike — 109 repos and the full infection chain to StealC (04/2026)
- Trend Micro — SmartLoader in fake GitHub repos, and the predecessor Water Kurita campaign (2025)
Comments
Join the discussionNo comments yet. Be the first to comment.





