Found a GitHub Follow Botnet. Then I Found the Operator.

This is Part 2 of an ongoing investigation. Part 1 documented the initial discovery of a coordinated GitHub follow botnet — 8 accounts with Jaccard following-list similarity of 0.99+ across ~29,800 entries each, evading cross-follow detection entirely.

In Part 1, I identified 8 accounts operating as a coordinated follow botnet and showed the statistical fingerprint that exposed them. After publishing, I kept pulling.

Subsequent analysis expanded the cluster to 9 accounts, recovered infrastructure linkage to a specific GitHub identity, and mapped the generation pipeline responsible for all 552 repositories across the cluster. The pipeline left recoverable artifacts in every repository it produced.

Here is what the data shows.

The Cluster Expanded

Running Jaccard similarity analysis against the original 8 accounts and their extended follower graphs surfaced a ninth account: lynewinter.

Jaccard similarity against mariwatts: 0.9898.

The same methodology that identified the original 8 applies here. A similarity coefficient of 0.9898 across ~29,800 following entries does not occur between unrelated accounts. At this scale, unrelated GitHub users do not organically converge on near-identical follow graphs.

The confirmed cluster is now 9 accounts:

canestein, hazexone, domcomit, kylehyne, jaderytm,
vierystein, hanyvert, mariwatts, lynewinter

552 Repositories, One Pipeline, 34 Minutes

Each of the 9 accounts has between 57 and 63 public repositories. Total across the cluster: 552 repositories.

Every repository was created on May 12, 2026. Fetching the first repository per account and extracting an HTML comment embedded in every generated README reveals the exact creation timestamp and a job identifier for each:

2026-05-12 11:10:39  |  hanyvert    |  SwapLink    |  job=48099
2026-05-12 11:18:46  |  jaderytm   |  GasSync     |  job=39412
2026-05-12 11:27:52  |  hazexone   |  BitForge    |  job=63871
2026-05-12 11:30:00  |  canestein  |  BlockLink   |  job=51606
2026-05-12 11:33:07  |  mariwatts  |  MintChain   |  job=82564
2026-05-12 11:35:37  |  vierystein |  HashSync    |  job=20845
2026-05-12 11:38:58  |  kylehyne   |  SmartLink   |  job=38575
2026-05-12 11:42:07  |  lynewinter |  YieldChain  |  job=78012
2026-05-12 11:44:30  |  domcomit   |  ProjectCloud|  job=26977

All 9 accounts provisioned across a 34-minute window. Job IDs are non-sequential, consistent with a job queue system dispatching parallel workers rather than a simple sequential script.

The comment format embedded in every README:

<!-- fallback_BlockLink_20260512113000_51606 -->

fallback_ is a named prefix — consistent across all accounts and repos. The timestamp and job ID vary per generation run. This is a recoverable generator fingerprint present in all 552 repositories.

The central finding of Part 2 follows directly from this: a single automated pipeline provisioned all 9 accounts and generated all 552 repositories in a 34-minute window. Every subsequent finding — the template leak, the commit metadata, the email domain — is evidence of that same pipeline’s operation.

Repo Contents Are Hollow

Fetching file trees and raw content from sampled repositories confirms the repositories contain no functional code.

A representative Python file (blocklink.py, 1,656 bytes):

class BlockLink:
    def run(self) -> bool:
        try:
            self.logger.info("Starting BlockLink processing")
            # Add your main logic here
            self.logger.info("Processing completed successfully")
            return True

The comment # Add your main logic here is the entirety of the implementation. Every repository follows this pattern: a class stub, a logging setup, an argparse entry point, and a test file that imports and instantiates the stub. No logic. No purpose. The repositories exist as props.

Repo names follow a consistent [Word][Suffix] pattern where suffixes are drawn from a fixed set: Core, Chain, Sync, Vault, Forge, Link. Names and descriptions are AI-generated. Stars: zero. Forks: zero, across all 552.

A Template Leak Confirms Shared Generation

The LICENSE section of every generated README contains a hardcoded URL pointing to mariwatts regardless of which account’s repository it appears in:

From canestein/BlockLink:

See the LICENSE file at https://github.com/mariwatts/BlockLink/blob/main/LICENSE

From lynewinter/YieldChain:

See the LICENSE file at https://github.com/mariwatts/YieldChain/blob/main/LICENSE

The account name substitution in the generator did not apply to the LICENSE URL field. mariwatts appears as the base account in the generator template. The substitution pattern replaced account names in repo metadata and README body text but missed this field.

Pipeline Linked to a Single GitHub Account via Authenticated Commit Metadata

Every repository across all 9 accounts contains commits with the following co-author line:

Co-authored-by: Hajigur <66867581+hajigur69@users.noreply.github.com>

The format [email protected] is GitHub’s authenticated noreply scheme. GitHub generates this address from the account’s internal numeric ID. The ID is immutable and assigned at account creation.

Fetching commits from hajigur69‘s own repository (Cloud9, created February 2, 2026) confirms the same identifier:

Author: Hajigur | 66867581+hajigur69@users.noreply.github.com

The same authenticated GitHub ID (66867581) appears both in the cluster commits and in commits authored directly by hajigur69.

hajigur69 is pipeline-linked. GitHub account created June 13, 2020. 903 followers. 679 following. Public bio: lamer.

Pipeline-Associated Infrastructure: carox.tech

Two accounts in the cluster — canestein and lynewinter — use a custom domain in their git commit author fields rather than GitHub’s noreply format:

canestein  →  [email protected]
lynewinter →  [email protected]

These addresses appear in the git author field of every commit on those accounts, recorded at commit time by the pipeline’s local git configuration.

WHOIS on carox.tech:

Creation Date:  2025-07-19
Updated Date:   2025-08-01
Registrar:      Namify Domains Inc
Name Server:    raphaela.ns.cloudflare.com
Name Server:    uriah.ns.cloudflare.com

DNS enumeration:

A record:   none
MX records: route1.mx.cloudflare.net (priority 44)
            route2.mx.cloudflare.net (priority 84)
            route3.mx.cloudflare.net (priority 28)
TXT record: v=spf1 include:_spf.mx.cloudflare.net ~all

No web presence. MX records point to Cloudflare Email Routing — a free service that forwards custom domain email to an external inbox. The domain functions exclusively as an email forwarding layer. The destination inbox is not publicly recoverable.

The 12-day gap between creation and update date is consistent with the time to configure email routing after registration. Beyond that, the domain predates the repo generation by several months — it was in place before the pipeline ran, not stood up alongside it.

Additional Artifact: Malformed Co-Author Address

In addition to the hajigur69 co-author signature, a second co-author line appears on the GOOD commit across 8 of the 9 accounts:

Co-authored-by: v <v@users.noreply.github.com>

GitHub’s authenticated noreply format is [email protected]. The numeric ID prefix is machine-generated by GitHub’s credential system at authentication time — it cannot be produced by typing a username, only by pushing through a properly authenticated git session. The address [email protected] contains no such prefix and does not match the format GitHub generates for any account.

There is a GitHub account with the login v (ID: 627846, created 2011). Its correctly formatted noreply address would be [email protected]. The string present in these commits is not that address.

The most likely explanation is a misconfigured git identity on whichever machine ran the pipeline — a user.email set to [email protected] rather than the proper authenticated format. Whether placeholder or misconfiguration, the effect is the same: it was baked into the pipeline’s commit template and executed across the majority of accounts unchanged. It’s a pipeline artifact, not a deliberate attribution.

What the Repos Are Actually For

The following is inference from the confirmed findings above, not an independently verified claim.

The repositories are not the product. Hollow stubs with no stars and no forks have no value on their own.

The follow botnet from Part 1 is the product. The accounts need to look like real developers for the botnet to work — real developers have repositories. The 552 repos are set dressing: enough surface area to pass a casual profile inspection without requiring the operator to produce anything functional.

The pipeline’s role is to manufacture that surface area at scale and speed. Nine accounts, 552 repositories, 34 minutes. A human doing this by hand would take weeks and produce inconsistent output. Automation produces it uniformly and leaves the fingerprints documented above.

What the inflated follower counts are ultimately used for — social proof laundering for phishing accounts, legitimacy for malicious package distribution, resale of aged accounts — is not established by this data. The confirmed finding is the coordination and the shared infrastructure. The downstream use is an open question.

The follower graph itself appears to be the primary maintained asset. ~29,682 developers curated into a target population, followed across 9 aged accounts. The repos are disposable scaffolding.

Summary of Confirmed Findings

Finding Source
9 coordinated accounts, Jaccard 0.98–0.99 API-derived following list comparison
All 552 repos created May 12 in 34-minute window Embedded HTML comment timestamps
<!-- fallback_NAME_TIMESTAMP_JOBID --> in every README Direct file fetch, all accounts
mariwatts hardcoded in LICENSE URLs across foreign accounts Direct file fetch, multiple accounts
66867581+hajigur69 co-author on all puppet account commits Raw commit data, GitHub API
66867581+hajigur69 author on hajigur69’s own repo Raw commit data, GitHub API
[email protected], [email protected] in commit author fields Raw commit data, canestein, lynewinter
carox.tech: no A record, Cloudflare MX, registered July 2025 WHOIS, DNS
[email protected] lacks numeric ID prefix Raw commit message, canestein/BlockLink
All repo code: hollow stubs with no implementation Direct file fetch
Zero stars, zero forks across all 552 repos GitHub API

What This Demonstrates

The finding is the pipeline. A single automation system provisioned 9 apparently independent GitHub accounts, generated 552 repositories across all of them in 34 minutes, embedded the same co-author identity in commits across the cluster, and left recoverable fingerprints in every generated file — all while the accounts maintained behavioral patterns designed to evade the most common detection heuristic (cross-following).

Attribution was possible here because of operational mistakes: a hardcoded template account, an unformatted co-author string, and email domain leakage in git config. A pipeline with better hygiene — consistent noreply formatting, template substitution coverage, and throwaway email addresses — would produce the same coordinated behavioral output with substantially less recoverable linkage.

This analysis identifies operational linkage and shared infrastructure; it does not independently establish the real-world identity of the individual operating the pipeline.

The hajigur69 co-author fingerprint that surfaced in this investigation didn’t start here. The same identifier — same numeric ID, same fallback_ generator artifact — appears in an earlier operation running nine months before this botnet was provisioned: a crypto spam network with fabricated commit histories, purchased star services, and 163 active pip install vectors targeting developers through search poisoning.

Part 3 covers that earlier operation and what it tells us about the infrastructure behind both.

Read Part 3 →