Never Deleted, Only Re-Pointed: Inside the 17,600-Repo FakeGit Fleet That Re-Arms Overnight

12.10.2026  APIIRO  Hack

Never Deleted, Only Re-Pointed: Inside the 17,600-Repo FakeGit Fleet That Re-Arms Overnight

Around 06:30 UTC on October 4, a script started editing READMEs across FakeGit, the network of fake GitHub repos that delivers the SmartLoader malware loader. Over the next thirty-four hours, more than thirteen thousand GitHub repositories were pushed in coordinated bursts, at up to 2,999 an hour. In the commits we sampled, 97% touched only the README, and 88% pointed its “Download” button at a ZIP that installs SmartLoader. Nobody had to create a single new repo. The fleet was already there. It just got re-aimed.

We call this RePointing: keep a trusted, long-lived repo, and swap only where its download button points.

TL;DR

17,610 FakeGit repos on GitHub are serving SmartLoader right now, and thousands of them were publicly reported months ago. We verified a SmartLoader ZIP behind every one. That is nearly double the largest list published before.

  1. Removal is list-driven, and the lists cover a fraction of the fleet. Orchid’s 9,330-repo list from June is 99.3% gone. Island’s 7,600-repo list from July is 85% still up, 77 days on. Only 49 of Orchid’s repos are in today’s fleet, so the takedown barely touched what runs now.

  2. One coordinated push re-armed nearly 14,000 repos in 34 hours. On October 4–5, 79% of the fleet was re-pushed in waves of up to 2,999 repos an hour. No new repos: in 97% of the edits we sampled, only the README changed. We call it RePointing. A fourth wave followed while we were writing, and another on October 6.

  3. Blocklists barely reach the people who click. 71% of the fleet was missing from URLhaus before our report, and a domain-level DNS blocklist can’t block one file on GitHub without blocking GitHub. Of the unlisted ZIPs we sampled, security engines on VirusTotal had seen almost none. Of the ZIPs URLhaus had listed, 99.96% still downloaded. Delete one file and the operator can point the lure at a spare copy: a fork, an older ZIP, a release asset or an issue attachment.

  4. Hundreds of real developers’ accounts are carrying it. Most of the 15,225 accounts are throwaways, but about 700 appear to be real developers’ accounts; 219 have history that other people created and that can’t be backdated. The lure commits were injected into them centrally, and the operator was still pushing to them on October 6. They pushed the kit into 270 repos they don’t own, including course and team projects.

Also inside: the operator’s C2 changelog, written to a public blockchain 21 times and the kit hidden in 167 AI skill folders.

What’s new here

GitHub malware campaigns usually scale by making more: Apiiro’s repo-confusion research counted over 100,000 automated malicious clones, and Check Point’s Stargazers Ghost Network ran 3,000+ fake accounts. This fleet barely makes new repos. It keeps the old ones and moves the download link, a mechanism a GitHub Community thread spotted in January and Orchid documented in June as recurring README-only re-pushes. Earlier reports found the lures, from SmartLoader’s first fake repos in early 2025 to Island’s 7,600-repo list in July (all under Prior work). What we add, beyond measuring the whole fleet, is the connections between things they saw separately:

The fleet: every verified repository, clustered by payload hash and colored by the wave in which it was last pushed

The fleet: every verified repository, clustered by payload hash and colored by the wave in which it was last pushed

Each dot is one repository, grouped around the obfuscated Lua payload it ships (120 distinct payloads) and colored by the wave in which the operator last pushed it. Every payload cluster mixes every wave: these are fleet-wide waves, not separate campaigns.

FakeGit and SmartLoader timeline, February 2025 to October 2026

FakeGit and SmartLoader timeline, February 2025 to October 2026

What the operator did (top) against what was publicly reported (bottom).

Inside the ZIP: what SmartLoader does

The FakeGit attack flow, from a copied project to a stolen browser session

The FakeGit attack flow, from a copied project to a stolen browser session

From lure to loot. The repo copies a real project, the README sends every click to a ZIP, and the ZIP runs SmartLoader, which finds its C2 on a blockchain and installs the StealC infostealer.

A lure takes a real project’s name, rewrites the README into a friendly install guide, and adds a download badge pointing at a ZIP buried in the repo’s own tree. A typical one, from a throwaway account (owner redacted, links removed):

A lure README: a friendly install guide whose Download badge and “Releases page” link both point at a ZIP inside the repo

A lure README: a friendly install guide whose Download badge and “Releases page” link both point at a ZIP inside the repo

Note step 3 of the install: “Windows may show a security screen. If it appears, click More info, then click Run anyway.” The guide tells the victim to click through a Windows SmartScreen warning.

The ZIPs carry fake version names like Software-v1.8-beta.2.zip and live where nobody looks: src/, assets/, docs/ and, in about 375 repos, the folders reviewers are trained to skip entirely: node_modules/, .github/, __pycache__/, dist/ and vendor/. Many more hide behind a dictionary. 5,474 lures put the ZIP in a top-level folder named with an obscure English word (ophthalmoscopic/, Arverni/). 99.6% of those words appear letter for letter, capitals included, in the 1934 Webster’s word list that ships with macOS and BSD as /usr/share/dict/words, and almost every one is used only once. The version numbers are just as mechanical: 83% of ZIPs carry one, always between 1.0 and 3.9, evenly spread, never 4.x and never a patch number.

Open one and you won’t find a project. You’ll find three or four files, with names that change from repo to repo but the same roles every time:

Detection is uneven: of the 120 distinct payload scripts we hashed, 106 are flagged on VirusTotal, and some of the newest currently score 2 to 9 of about 75 engines.

After the double-click

Derp, Hexastrike and Island have taken the payload apart, so in short: the launcher hides its window, profiles the machine (including a lookup to ip-api.com, which VirusTotal’s sandbox recorded for a payload shipped by 1,145 of our repos), reads its current C2 address from a Polygon smart contract (more below) and pulls the next stage from other GitHub repos. In published analyses, the chain ends with the StealC infostealer 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 the login and the MFA prompt entirely.

The kit keeps evolving

We dated the kit across the fleet from the LuaJIT build string and PE timestamps inside each ZIP, read in memory without extracting a file:

New wrapper, same engine.

RePointing: re-armed, not respawned

On October 4–5, 79% of the fleet was re-pushed, about 77% of it in three tight waves of four to five thousand repos each, peaking at 2,999 repos in one hour. We sampled 441 re-pushed repos at random where the edit could still be recovered (many were force-pushed over and no longer show in branch history). In 97% of them the edit touched only the README, and in 88% it pointed the Download link at a kit ZIP we had verified; most of the rest pointed it at a GitHub Pages site.

A re-push commit on a throwaway lure: every changed line swaps one kit ZIP link for another

A re-push commit on a throwaway lure: every changed line swaps one kit ZIP link for another

One lure’s re-push (owner redacted, URLs defanged). The commit touches only the README, and every one of its 12 changed lines swaps the old ZIP link for a newer kit ZIP already sitting in the same folder.

Lure repositories by hour of last push, UTC

The waves don’t hit repos at random. The first two are the same program run twice, with near-identical minute-by-minute profiles, and each walks the lures in the order they were first armed, about 1.6 pushes a second, each from a different account: a pool of workers working through one sorted inventory.

And 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, usually their newest; picking repos at random would hit two or more on about 92% of them. The older lures on the same account stay up, untouched and still downloadable.

At 20:00 UTC on October 5, while we were drafting this post, a smaller fourth wave re-pushed 388 lures within the hour, mostly from one leftover cohort of accounts.

Then, on October 6, as we were finishing this post, it went again at fleet scale. By midday UTC, 8,558 of the 14,158 lures we had re-checked so far (60%) had been pushed that day, in two bursts around 05:00–07:00 and 10:00–12:00 UTC, and the second was still running when our check ended.

The tooling leaves a fingerprint: unsigned commits with the web editor’s default message (“Update README.md”) and a +0000 timezone, and fresh accounts committing from one email pattern, <first><last><four letters>@hotmail.com or outlook.com. The accounts come from a farm: 750 were registered on April 9, 2026 alone and left to age before getting a lure.

Why work this way? A repo that has existed for months, with a real project’s name and history, earns trust that a fresh fork doesn’t. Keeping the repo and swapping only the link keeps that trust, at a cost of one commit per repo. We found no sign it’s a search-ranking trick: in 120 GitHub searches for lure names, re-pushed lures ranked no better than lures left alone. It’s simply cheaper. Most of the live fleet stays up, so there’s little to rebuild; the operator only has to move the link.

Listed is not removed

Being on a blocklist is not the same as being taken down. Four findings show the gap.

Most of it was never listed. GitHub serves a raw file from two addresses, with identical bytes. In our URLhaus snapshot, their coverage is very different:

Blocklist coverage by link format

Collection pipelines look for raw.githubusercontent.com, because that’s where raw files are served from. The github.com/…/raw/ path redirects there but doesn’t match the pattern. Our own first sweep made the same mistake: fixing one regex surfaced thousands more lures in data we’d already downloaded.

It also matters who reads the list. URLhaus feeds DNS blocklists, IDS rules and antivirus, and shares with Google Safe Browsing, but a DNS block can’t stop one file on github.com without blocking GitHub. What’s left is URL- and file-level checking, and that only knows what’s listed: in a sample, all 38 URLhaus-listed lure URLs were flagged by at least one VirusTotal engine, while VirusTotal had seen only 6 of 38 unlisted github.com/…/raw/ ones.

The part that was listed stayed up. In the URLhaus-listed part of the fleet, 97% of the October re-pushes pointed the README at a ZIP URLhaus had listed in June, and 99.96% of listed ZIPs were still downloadable. A blocklist entry protects people whose tools check it. But blocklists and code hosts aren’t wired together: a listing doesn’t remove the file, and it doesn’t stop the operator from pointing the same button at the same file again.

What did get removed is what was on Orchid’s list. Orchid’s June list is 99.3% gone, almost entirely as banned accounts. Island’s July list, 77 days on, is 85% still up; Hexastrike’s April list, 24 weeks on, about half. Island repos that also appeared on Orchid’s list are 71% gone, against 14% for the rest.

The timing suggests the operator noticed. The last mass build day before Orchid published was June 17, 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.

Deleting the linked file isn’t enough either. The README link is only the copy that’s currently advertised. In the parts of the fleet we checked for spares:

RePointing works because there is always something to point at.

Hunting this campaign? Match both raw.githubusercontent.com/<owner>/<repo>/…zip and github.com/<owner>/<repo>/(raw|blob)/…zip. The second form is most of what’s missing.

Real developers’ accounts, and their teammates’ repos

15,225 accounts hold 17,624 repos, so banning an account removes little more than one lure. 14,219 of them (93%) look like throwaways: 9,197 created since July 2024, and 5,022 older accounts with nothing real on them before the lure arrived. Age and inactivity are signals, not proof; some of those may be dormant accounts that were bought or taken. But some accounts are clearly not throwaways.

rushter.com also suspected account hijacking in March, and Derp named five hijacked developer profiles the same month. Island describes the fleet’s accounts as “lookalike profiles”, and most are empty or copied-project accounts. Hundreds aren’t.

We flagged 965 accounts as possible takeovers: 173 established developers with years of real contributions, and 792 more that owned a repo for at least a month, with three or more of the owner’s own commits over a week or longer, before the ZIP appeared. (Cloned lures also copy the original project’s commit history, so we count only the owner’s own commits, not the copied ones.) Then we tested them against evidence that can’t be backdated: pull requests merged by other people, write access granted to other people’s repositories, comments, forks and issues by other people, all with GitHub’s own timestamps. 219 of the 965 have that kind of third-party history before the kit appeared, usually years of it; 4 of 200 throwaway accounts do. About 200 more show years of the owner’s own work. A hand check of 180 of the 792 found two-thirds (95% interval 60–73%) to be real people’s accounts and the rest accounts the operator had aged on purpose, so the total is roughly 700, of which 219 are proven one by one. None of 120 we checked imitates an older username: they are the original accounts.

The switch shows in the commits. None of the 951 lure commits we 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 of these ZIPs landed on February 19–23, 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. At least 80 owners went on using their accounts afterwards, merging pull requests and creating repos, with no sign they noticed; only 2 ever deleted the ZIP, and we found no public “I was hacked” posts.

And the operator still has that access. Accounts taken over in February were pushed again in October: since October 4, 40% of the lures on established developers’ accounts and 51% of those on the history-flagged ones were re-pushed (59% and 74% where the account holds a single lure). We sampled 12 established developers’ lures pushed on October 6; all 12 got the operator’s unsigned “Update README.md” commit inside that day’s wave windows, touching nothing else. Seven months on, the owners show no public sign they’ve noticed.

Holding an account this long suggests a long-lived credential, such as a token or key, rather than a single browser session. The stealer this fleet delivers is documented taking browser cookies and saved passwords, which would include a github.com login, not token files or SSH keys; a stolen session could still be used to mint a token or add a key. We can’t see from outside which credential it is or how it was first obtained, and for long-dormant accounts we can’t rule out a sale.

Then that access was used again. Between February 1 and March 19, 139 of these accounts pushed kit ZIPs into 270 repositories they don’t own (269 still infected): classmates’ projects, team capstones and course repos under university and bootcamp organizations. All 314 planted files are the kit, and 313 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 community OpenWrt package feeds in late September, ZIP included. Their build scripts install only the add-on’s Lua files, so router images didn’t carry the kit, but the malware sat inside feeds that firmware builds clone. Both feeds dropped the package within a day of our public report.

We found no public sign the victims ever touched a lure before their takeover, though downloading a ZIP leaves no trace.

The C2 changelog is on a blockchain

How do seventeen thousand ZIPs keep working when C2 servers keep getting burned? SmartLoader doesn’t hardcode one. It asks a smart contract on Polygon where to go next.

That lets the operator move the C2 without touching a repo, but every move is a public, permanent transaction. Reading the contract’s history (0x1823A9a0Ec8e0C25dD957D0841e3D41a4474bAdc, created 2025-11-15) gives the operator’s C2 changelog for this contract without sending a packet to their servers.

Derp documented this contract first, in March, with the operator’s wallet; akj.io rebuilt 15 of its updates in July. Counting every transaction the wallet has sent, against its nonce, gives 21 updates between November 2025 and September 2026, about one every two weeks, including two addresses not in any public reporting we found. (A block explorer’s list was missing 5 of the wallet’s 24 transactions, so count it yourself.) The highlights, lined up against the fleet’s activity:

New kit ZIPs per week (blue) and every C2 change on the Polygon contract (red), September 2025 to October 2026

The 21 updates came at irregular gaps, from hours to six weeks, through busy and quiet stretches alike, including the summer lull when almost no new lures appeared. The October waves didn’t coincide with one.

The AI-developer angle

4,551 lures (26%) share their exact name with a real project that has at least 50 stars (16.6 million stars in total). 46% of those lures copy AI, MCP, Claude or agent tooling: mattpocock/skills (26 lures), anthropics/claude-code (12). They borrow trust through the names, not stars: 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, as they stood on the day the lure was made, a median two weeks after the original appeared; 9 more carry part of it. A copy of NVIDIA’s garak scanner keeps 525 of its files unchanged; a copy of sapientinc/PRAXIST appeared two days after the project did, with nearly 4,000 of its files. What’s added is the ZIP and a README whose links were repointed to it. The rest borrow the name, with other files or none. 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 command 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. The lures don’t rely on this route; their READMEs link the ZIP directly. And landing on disk isn’t running: 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 that has your keys.

Written by a model, shipped by a script

The lure text reads machine-written. 87% of repo descriptions on the fleet’s new accounts run past 100 characters, against 4% of random GitHub repos; 31% open with an emoji and 38% with a command verb (“Build…”, “Automate…”), against well under 1% of random repos for either. The READMEs follow the same register: friendly, numbered, padded with system requirements. Trend Micro flagged AI-written lures for this malware in 2025, so the writing itself isn’t news. What’s new is what the text gives away about how it was made.

Nobody reads it before it ships. In a sample of 2,191 lure READMEs, 17 begin with a stray ````markdown` 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”. None of the 157 human-written READMEs of the hijacked real projects we used as a control has any of these. And each one is generated fresh: among 2,196 READMEs we found 2,116 distinct section layouts and only four exact duplicates. The headings shift on particular days, as if someone revised the prompt, but we found no link to public model releases.

The delivery is mechanical. In the October waves, pushes came about 1.6 a second, in the order the lures were first armed. In the February takeovers, commits landed a median 23 seconds apart, never between midnight and 5 a.m. UTC, and 2,194 of 2,385 copied the previous commit’s message word for word. The folder names that hide the ZIPs come from Webster’s 1934 dictionary file, a word list any script can read, not a model’s choice. The February takeovers were two commits each: one adding only the ZIP but titled “Update README.md”, then a README rewrite a median two hours later, in all 59 pairs we diffed, with no fix-ups, reverts or retries anywhere. As we read it, a script calls a model once per repo and publishes whatever comes back, on a human working schedule. That isn’t an AI agent running the campaign; it’s a pipeline nobody proofreads.

How big is it really?

Every new angle we tried found more. Commit metadata, GitHub’s public event archive, the GitHub Pages hop and the other repos of accounts we’d already found together added about 3,000 lures that no keyword search had surfaced.

Comparing our search with URLhaus’s independent collection (a Lincoln–Petersen estimate) suggests we find roughly 84% of lures using the raw.githubusercontent.com form. If the other link forms are found at a similar rate, the live fleet is around 21,000 repos. That rests on assumptions that don’t fully hold, such as two independent collections with similar blind spots, so read it as a rough size, not a bound. A second estimate, against Island’s independent July list, lands at about 22,000. The hard number is what we saw: 17,610 live lure repos at our final snapshot.

What to do

Developers and AI-agent users

Security teams

Platforms and directories

How Apiiro helps

This fleet lives in the gap between “listed” and “blocked”, and it also targets the coding agents that install tools on a developer’s behalf. Guardian Protect, part of Apiiro’s Guardian Agent, works on the developer’s machine. It checks the install commands a coding agent wants to run against policy (known-malicious and low-reputation packages, and dependencies pulled from somewhere other than the real registry) and blocks them before anything is fetched or executed. It also inventories the dev tools and open-source components already on the endpoint.

Get a demo of Guardian Agent.

Disclosure

Indicators

SmartLoader payload clusters (SHA-256 of the obfuscated Lua payload):

C2 dead-drop: Polygon contract 0x1823A9a0Ec8e0C25dD957D0841e3D41a4474bAdc; newest entries 194.48.248[.]94 (2026-08-11) and 185.10.68[.]110 (2026-09-17). Read from the chain only, never contacted.

Full repository, URL and hash lists are available to defenders on request.

Prior work

This fleet was found piece by piece. In order (the first entry and rushter.com describe the same playbook but don’t analyze the payload, so we can’t confirm it’s the same kit):

Method

GitHub GraphQL search across README templates and ZIP-name patterns in adaptive push-time windows, widened with commit metadata, GH Archive events (the hunting approach Orchid published in June), GitHub Pages and cross-repo links. For every candidate we fetched the README and each linked ZIP and matched the ZIP against the kit structure in memory. Nothing was extracted or executed, including the other families. VirusTotal was used for hash lookups only; C2 infrastructure was observed passively (on-chain data, DNS and RDAP). Numbers come from a freeze at 2026-10-06 07:41 UTC. “The operator” is shorthand for whoever controls the coordinated pushes; the evidence shows shared tooling and timing, not a single named actor. Account categories (throwaway, takeover) are heuristics from public account and commit history.

We also cross-checked the published lists from Island (7,600 repos), Orchid (9,330) and Hexastrike (109). 6,237 of Island’s repos were already in ours. The few still serving the kit that we lacked, 67 from Island and 1 found with Orchid’s search pattern, are included in our counts.