# wfx GitHub Actions CI secret stealer via hijacked GitHub accounts (November 2026): hunting and checking pack

CybaVerse pack, 12 Nov 2026. All times are UTC, which is also UK time (GMT) this week. No CVE: this is account takeover
and CI abuse, not a software flaw. **Critical by impact** is our framing (not a vendor rating): where the stealer ran,
every secret and cloud role the repository's workflows could reach should be treated as stolen.

Page source: The Hacker News, "Attacker Used 4,837 Hijacked GitHub Accounts to Run a CI Secret Stealer in 1,806+
Repositories", published 12 Nov 2026 06:42 UTC (RSS feed time), with a correction notice added later the same day.
https://thehackernews.com/2026/11/4837-hijacked-github-accounts-ran-ci.html
Findings come from security researchers. Indicators are credited to "the published research, as reported by The Hacker
News" (label **THN**) when the THN page carries them, and to "published research" (label **RESEARCH ONLY, not on the THN
page**) when it does not. **OURS** marks what CybaVerse checked itself in public registry data on 12 Nov 2026.

## What happened (facts with times)

| When (UTC) | What | Source |
|---|---|---|
| Tue 10 Nov 2026 | Campaign starts ("Starting Tuesday") | THN |
| 10 Nov, 12:45 | First probe pull requests | RESEARCH ONLY (THN gives no probe start time) |
| 10 Nov, 12:46 to 12 Nov, 04:36 | 8,093 repositories receive malicious commits (default branches of public repositories only, so the true total is higher); 7,309 by workflow injection, 771 by npm postinstall hook | THN |
| 10 to 11 Nov | 4,837 accounts open probe pull requests titled `docs: dependency audit notes` from branch `wfx-docs` in 61,764 repositories; 10,431 of the probes went in through collaborator or organization access, 92% of those still open | THN |
| 10 to 11 Nov | The stealer ran in at least 1,806 repositories; its runs and logs were deleted | THN |
| 11 Nov, 09:55:32 | Go pseudo-version `github.com/vkuznet/gocurl v0.0.0-20261111095532-8d5aad0cb0da` (the attacker's commit; the workflow has never run) | THN; time from the Go module proxy (OURS) |
| 11 Nov, 12:29:20 | Packagist `digitalelvis/neuronai-studio` 3.14.3 published with the stealer and an npm postinstall hook | THN; time from Packagist (OURS) |
| 12 Nov, 05:04 to 05:38 | Three more probe pull requests from `wfx-docs` branches (THN's own check); campaign still active | THN |
| 12 Nov, 06:42 | THN article published | THN RSS feed |
| 12 Nov, 05:19:40 | Go `docker-env` and `redock` v1.0.282 published with a Telegram-only stealer workflow | RESEARCH ONLY (time from the Go module proxy, OURS) |
| 12 Nov, 07:50:45 | Packagist `digitalelvis/neuronai-studio` **3.14.4** published, again adding a `.ci-xxxxxx.sh` script (`.ci-uxe0r4.sh`) and a postinstall hook; its settings send data only to the wfx Telegram chat | OURS (GitHub tags and Packagist metadata, read 09:20 UTC) |
| 12 Nov, 08:59:22 | Go `docker-env` and `redock` **v1.0.284** published with the stealer in `.github/workflows/beta.yml` again (Telegram variant; the odd releases around it are clean) | OURS (Go module proxy, read 09:36 UTC) |

Other facts:

- **Delivery** (THN): probe pull request first, then one of three payloads: `ci: refresh build cache` adds about 1,500
  lines to an existing workflow; `chore: add ci helper` adds `.ci-xxxxxx.sh` and a `postinstall` hook in `package.json`
  (runs on any `npm install`, including on laptops); `chore: add dependency audit workflow` adds
  `.github/workflows/build-audit-xxxxxx.yml` on a `chore/` branch (after `chore: initial commit` in empty repositories).
  The attacker then pushes `ci: restore` and `ci: cleanup`, usually reverts within minutes, and deletes the run. A
  missing run number after the attacker's commit is the more reliable sign the stealer ran; the absence of `wfx-loot`
  artifacts, `wfx-` cache entries or `WFX-BEGIN` step summaries proves nothing.
- **What it steals** (THN): Actions secrets and variables, `GITHUB_TOKEN`, OIDC and Actions runtime tokens, AI service keys,
  secrets in the memory of the runner's processes, credential files for AWS, Azure, GCP, Kubernetes, npm, PyPI, SSH and
  other tools, the repository with its last 100 commits and recent artifacts; on self-hosted runners also instance
  metadata credentials, Docker container state and systemd units. It has settings for private
  repositories, which suggests it may also be running in them.
- **Escalation and persistence** (THN): swaps GitHub OIDC tokens for AWS, GCP and Azure sessions; creates long-lived AWS
  IAM access keys; can rewrite an AWS role trust policy to allow any AWS account; adds `wfx-ci-` federated identity
  credentials to Entra (Azure AD) applications, which survive token rotation.
- **Exfiltration** (THN): HTTP POST to `194.150.166[.]244:8080/exfil`, `18.143.168[.]96:8080/exfil` or
  `153.75.82[.]159:8080`, or a Telegram chat (about a fifth of 200 sampled payload commits used Telegram only).
- **Packages**:
  - Packagist `digitalelvis/neuronai-studio` **3.14.3** is malicious (THN). A normal `composer install` does not run it;
    `npm install` inside the package's vendor folder does.
  - **Correction, OURS:** Packagist's own malware list also flags **3.14.2** (19 Oct 2026; it added a fake font file and a
    VS Code task, which matches the October wave), and **3.14.4** appeared on 12 Nov with a new stealer script. So "pin
    3.14.2 or earlier" is not safe: pin **3.14.1 or earlier**, and block 3.14.2, 3.14.3 and 3.14.4.
  - Go `github.com/onuragtas/docker-env` v1.0.278 and v1.0.280 carry the stealer only in a GitHub Actions workflow file;
    Go builds never run workflow files (THN). **RESEARCH ONLY, not on the THN page:** v1.0.282 (Telegram variant) is also
    poisoned, and the module is served under its new name `github.com/onuragtas/redock` too. **OURS:** v1.0.284 (12 Nov
    08:59 UTC) is poisoned the same way, so remove v1.0.278, v1.0.280, v1.0.282 and v1.0.284 under both paths; the
    odd releases v1.0.277 to v1.0.285 carry no stealer (checked in the docker-env module zips).
  - Go `github.com/vkuznet/gocurl` v0.0.0-20261111095532-8d5aad0cb0da: block it (THN).
  - 28 WebdriverIO repositories got commits authored as the bot account `wdio-bot`; no package release was affected, and
    the bot's token should be treated as stolen (THN). It is not confirmed stolen.
- **Attribution**: researchers found the accounts overlap with October's GhostAction wave (accounts hit in October were
  about 1,000 times more likely to be used) but do not attribute this campaign to the same actor (THN). We do not
  attribute it either.
- **GitHub**: no GitHub security advisory, GitHub blog post or GitHub changelog entry about this campaign was found at
  09:08 to 09:20 UTC on 12 Nov 2026 (rechecked at finish, see the end). The GitHub Advisory Database has no advisory for
  the three packages. We do not claim an official GitHub response.

## Indicators and where they come from

| Indicator | Source |
|---|---|
| Branches `wfx-docs`, `chore/[a-z0-9]{8}`, `ci-check-[0-9]{4}` | THN |
| Pull request title `docs: dependency audit notes` | THN |
| Commit messages `ci: refresh build cache`, `chore: add ci helper`, `chore: add dependency audit workflow`, `chore: initial commit`, `ci: restore`, `ci: cleanup` | THN |
| Files `.ci-xxxxxx.sh`, `.github/workflows/build-audit-xxxxxx.yml`, `postinstall` hook in `package.json` | THN |
| Stealer SHA-256 `89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf` | THN |
| `194.150.166[.]244:8080/exfil`, `18.143.168[.]96:8080/exfil`, `153.75.82[.]159:8080` (no path given on the THN page) | THN |
| Telegram chat as an exfil channel (no chat ID on the THN page) | THN |
| Entra `wfx-ci-*` federated credentials; CloudTrail `AssumeRoleWithWebIdentity` or `AssumeRole` sessions named `wfx-*` then `CreateAccessKey` or `UpdateAssumeRolePolicy`; GCP `GenerateAccessToken` and `SignJwt` from the GitHub workload identity pool; Vault JWT logins from the GitHub OIDC issuer | THN |
| Packages neuronai-studio 3.14.3, docker-env v1.0.278 and v1.0.280, gocurl pseudo-version; `wdio-bot` | THN |
| Probe start 12:45 UTC on 10 Nov | RESEARCH ONLY |
| `/exfil` path on `153.75.82[.]159:8080` | RESEARCH ONLY |
| `github.com/onuragtas/redock` and v1.0.282 | RESEARCH ONLY |
| Probe file `docs/wfx-audit.md`, probe body "Routine dependency audit notes.", workflow name `Dependency Audit xxxx`, job `audit_base`, steps `Pass runtime` and `Run audit` | RESEARCH ONLY |
| Payload settings `WF_CALLBACK`, `WF_CTOKEN`, `WF_TG_CHAT`, `WF_TG_BOTS`, `WF_MEMSCAN`, `WF_EXFIL_*`; Telegram chat ID `-1004340163187` | RESEARCH ONLY |
| Runner paths `.wfx-<pid>` under the runner temp folder, `/tmp/wfx-scan-<pid>/`, `wfx-<pid>.tgz`; AWS session names `wfx-<pid>`; Entra names `wfx-ci-<n>` | RESEARCH ONLY |
| Extra hashes: `.ci-e5infq.sh` in 3.14.3 `6960931e69bbcd70f9d0727c536251fa94167d0b8a988d12c5efe45e54b1d389`; `beta.yml` in docker-env/redock v1.0.278, v1.0.280, v1.0.282 (`a45d3ced...`, `73cec6bc...`, `7128295f...`) | RESEARCH ONLY |
| Telegram bot tokens and per-campaign `WF_CTOKEN` values | RESEARCH ONLY; deliberately not reproduced in this pack |
| neuronai-studio 3.14.2 flagged by Packagist; 3.14.4 with `.ci-uxe0r4.sh`, SHA-256 `91c82e1b370bfa37e66fcac423fc52939d87e9a987d85c61f1ef3f099fee250d` | OURS |
| docker-env and redock v1.0.284, `beta.yml` SHA-256 `63046ae820408da91a18d1b4b53bad8c78f244a266800dccb643658fb4ef0a78` | OURS |

We could not verify the THN SHA-256 against a sample: the per-repository settings at the top of each script change its
hash (the 3.14.3 and 3.14.4 scripts hash differently), so file-name and content checks matter more than the hash.

## Response steps from the security researchers (credited to security researchers)

As reported by The Hacker News unless marked:

1. **Any account that opened a probe pull request is compromised**, even if the stealer never ran.
2. **Revoke the commit author's tokens, OAuth app grants, SSH keys and sessions, and turn on 2FA.**
3. **Rotate every secret and variable the repository can read** (RESEARCH ONLY, not on the THN page: including
   environment and organization secrets, and any key the runner could reach). Where the stealer ran, treat every secret and cloud role available to
   the workflows as compromised.
4. **Anyone who ran npm install on an affected commit**: treat that machine as compromised and rotate its keys.
5. **Packages**: block neuronai-studio 3.14.3 (they say pin 3.14.2 or earlier; see our correction: pin 3.14.1 or earlier);
   remove docker-env v1.0.278 and v1.0.280 (we add v1.0.282 and v1.0.284, and the same versions under
   the redock path); block the gocurl pseudo-version; treat the `wdio-bot` token as stolen.
6. **Check cloud backdoors**: Entra `wfx-ci-*` federated credentials you did not create; CloudTrail `wfx-*` sessions
   followed by `CreateAccessKey` or `UpdateAssumeRolePolicy`; GCP `GenerateAccessToken` and `SignJwt` from the GitHub
   workload identity pool; Vault JWT logins from the GitHub OIDC issuer.
7. **Research only, not on the THN page**: delete the `wfx-docs` branch and close the probe pull request; pull any
   package version built between the malicious commit and the `ci: cleanup` commit and publish again from a clean commit.

## Our advice (OUR ADVICE, CybaVerse)

Not researcher guidance. Reactive first, then proactive.

1. **Hunt now**: run the checkers on your clones (fetch all branches yourself first; the checkers never fetch) and run
   the KQL. Look in the Actions tab for missing run numbers after any of the commit messages above.
2. **Rotate in order of reach**: cloud roles and deploy keys first, then registry tokens (npm, PyPI, Packagist, crates),
   then everything else in repository, environment and organization secrets. Rotation alone does not remove the
   backdoors: also delete `wfx-ci-*` federated credentials, revert any AWS trust policy that allows `*`, and delete IAM
   access keys created since 10 Nov that nobody can account for.
3. **Machines**: rebuild self-hosted runners that ran a hit workflow; treat developer laptops that ran npm install on a
   hit commit as compromised.
4. **Turn on 2FA everywhere** and require it at organization level.
5. **Short-lived, fine-grained tokens only**: replace classic PATs with fine-grained tokens scoped to named repositories
   with short expiry; prefer GitHub Apps; review OAuth app grants.
6. **Protect workflow files**: CODEOWNERS for `.github/workflows/` plus rulesets or branch protection that require review
   for changes there, and block direct pushes to default branches.
7. **Scope OIDC trust to repository and branch**: AWS trust policies and Entra federated credentials should match an exact
   `repo:ORG/REPO:ref:refs/heads/main` (or an environment) subject, never a wildcard; the checker flags GitHub OIDC trust
   with no `sub` condition for review.
8. **No secrets on untrusted branches**: put deploy secrets in protected environments limited to protected branches, set
   the default `GITHUB_TOKEN` permission to read, and use `pull_request` (not `pull_request_target`) for outside code.
9. **Alert on workflow run deletions** (`workflows.delete_workflow_run` in the GitHub audit log, Q1b) and on new
   federated credentials (Q2b); stream the GitHub audit log to Sentinel if you do not already.
10. **Egress**: GitHub-hosted runners cannot be firewalled by you; on self-hosted runners, restrict outbound traffic and
    alert on Telegram Bot API calls from build processes (Q5c).

## Contents

- `kql/github-actions-wfx-ci-stealer-2026-11-hunting.kql`: 17 hunts for Microsoft Sentinel and Defender XDR.
  - Q1 IOC (THN) GitHub audit log rows with the probe, commit and file indicators; Q1b IOC (THN) workflow run deletions;
    Q1c OUR PATTERN other actions by those accounts.
  - Q2 IOC (THN) Entra `wfx-ci-*` federated credentials; Q2b OUR PATTERN any new GitHub-issuer federated credential.
  - Q3 IOC (THN) AWS `wfx-*` sessions; Q3b IOC (THN) `CreateAccessKey` or `UpdateAssumeRolePolicy` from them; Q3c OUR
    PATTERN any trust policy opened to `*` and keys created by assumed roles.
  - Q4 IOC (THN) GCP `GenerateAccessToken` and `SignJwt` from a workload identity pool.
  - Q5 IOC (THN) Defender connections to the three servers; Q5b IOC (THN) the same in CommonSecurityLog; Q5c OUR PATTERN
    Telegram Bot API calls from build processes.
  - Q6 IOC (THN) stealer SHA-256 (extra hashes labelled); Q6b IOC (THN) `.ci-xxxxxx.sh` and `build-audit-xxxxxx` files;
    Q6c IOC (THN) postinstall running a `.ci-xxxxxx.sh`; Q6d IOC (RESEARCH ONLY) runner staging paths; Q6e OUR PATTERN
    reads of other processes' memory through `/proc`.
  - Vault: no standard Sentinel table; search your Vault audit log for JWT logins whose issuer is
    `token.actions.githubusercontent.com` since 10 Nov.
- `sigma/`: 6 rules: `proc_creation_lnx_npm_postinstall_ci_helper.yml`, `file_event_lnx_ci_helper_script.yml`,
  `net_connection_wfx_exfil_servers.yml` (IOC, THN); `file_event_lnx_wfx_staging_paths.yml` (IOC, RESEARCH ONLY);
  `aws_cloudtrail_wfx_session_start.yml`, `aws_cloudtrail_wfx_session_persistence.yml` (IOC, THN). `sigma/converted/`
  holds Kusto conversions. Documented gap: the Microsoft pipelines (microsoft_xdr, microsoft_365_defender,
  sentinel_asim, azure_monitor) have no table for AWS CloudTrail, so the two AWS rules convert only without a pipeline;
  use KQL Q3 and Q3b for Sentinel.
- `scripts/check-wfx-ci-stealer.sh` (bash) and `scripts/Check-WfxCiStealer.ps1` (PowerShell 7): same output and exit codes.
- `tests/`: bash tests, Pester tests with parity, static fixtures (FIXTURE_ONLY), `make_git_fixtures.sh` (builds synthetic
  git repositories outside the pack at test time), `kql_parse.ps1`, `run_all.sh`.

## The checkers

Offline and read-only. They never fetch, never write, and never print file contents or secrets (only paths, ref names,
short commit hashes, versions and credential or role names). With `--repo` they run only `git rev-parse`,
`git for-each-ref` and `git log` (including reflogs), with `GIT_OPTIONAL_LOCKS=0`, hooks and fsmonitor off, network
protocols refused, and `GIT_CEILING_DIRECTORIES` so git cannot wander into a parent repository.

```
bash scripts/check-wfx-ci-stealer.sh --repo ~/src/app --name app            # git clone: refs, commit subjects, files
bash scripts/check-wfx-ci-stealer.sh --dir ~/src/app/vendor                 # any folder: files only
az ad app federated-credential list --id APP_ID > fic.json                  # operator export, run by you
aws iam list-roles > roles.json                                             # operator export, run by you
bash scripts/check-wfx-ci-stealer.sh --entra-json fic.json --aws-json roles.json
pwsh -File scripts/Check-WfxCiStealer.ps1 --repo C:\src\app
```

What they look for: branches `wfx-docs` (found) and `chore/[a-z0-9]{8}`, `ci-check-[0-9]{4}` (review); commit subjects
(the three payload messages and the probe title are found; `ci: restore`, `ci: cleanup`, `chore: initial commit` are
review); `build-audit-xxxxxx.yml` workflows, `.ci-xxxxxx.sh` files (12 folder levels deep, vendor and node_modules
included), `package.json` postinstall hooks that run one, `docs/wfx-audit.md`; payload settings and the exfil addresses
inside workflow, shell and package files; the SHA-256 list; `composer.lock` with neuronai-studio 3.14.2, 3.14.3 or 3.14.4;
`go.sum` and `go.mod` with the poisoned Go versions (including v1.0.284, OURS) (review: Go builds do not run workflow files); `wfx-ci-*` federated
credentials; AWS trust policies open to any account, and GitHub OIDC trust with no `sub` condition (review).
`--extra-sha256` and `--extra-ip` add your own values. Each finding names its source (THN, RESEARCH ONLY, OURS, OPERATOR).

| Verdict | Exit | Meaning |
|---|---|---|
| INDICATORS FOUND | 1 | At least one strong indicator: treat the repository's reachable secrets as stolen |
| REVIEW | 5 | Only weak or pattern matches (generic commit messages, branch patterns, Go versions, loose OIDC trust) |
| NO INDICATORS FOUND | 0 | Nothing found in what was scanned; not proof (reverted commits, deleted runs, unfetched branches) |
| UNKNOWN | 3 | Nothing could be scanned (a safety net; a readable input always gives one of the verdicts above) |
| (usage or read error) | 4 | Nothing printed on stdout |

Limits: `composer.lock` must be in Composer's normal multi-line layout; URL-encoded AWS policies are reported for review,
not decoded; the GitHub side (pull requests, deleted runs, audit log) needs the GitHub UI, API or the KQL.

## Caveats and UNVERIFIED items

- The KQL in this pack has NOT been run against a live tenant. It parses offline (Kusto.Language) against an assumed
  schema subset. GitHub audit log table names depend on the connector you run, and column names beyond Action, Repo,
  Actor and CreatedAt are UNVERIFIED; what reaches the audit log depends on your GitHub plan and settings.
- UNVERIFIED: the THN stealer SHA-256 (no sample matched it); the count differences between write-ups (61,764 repositories
  and 4,837 accounts on THN versus 61,734 and 4,833 in one write-up, and 1,806 versus 1,835 repositories where the
  stealer ran in another); whether 3.14.4 was published by the attacker or the owner (the commits are authored as the
  owner, as in the earlier waves); whether Packagist will flag 3.14.4; who published docker-env v1.0.284 (the tag points at a
  commit with the stealer workflow, as in the earlier releases); whether the `wdio-bot` token was actually used.
- How the attacker got the account credentials is not known (THN).
- Neither write-up nor THN reports GitHub action against the accounts; we found no GitHub statement.

## Rechecked at finish (12 Nov 2026, 09:35 to 09:37 UTC)

- THN page: text unchanged since 09:08 UTC; still gives 12:46 UTC as the start of the malicious commits and
  `153.75.82[.]159:8080` with no path; still does not mention redock, v1.0.282 or v1.0.284.
- GitHub: no security advisory, blog post or changelog entry about the campaign; the GitHub Advisory Database has no
  advisory for neuronai-studio or docker-env, and none of its 100 newest malware advisories (newest 06:30 UTC) covers
  these packages.
- Packagist: 3.14.4 is still served and is still not on Packagist's malware list (3.14.2 and 3.14.3 are).
- Go module proxy: docker-env and redock now list up to v1.0.285; v1.0.284 is poisoned (OURS, above).
