Safeguard
Supply Chain Security•

wfx: 4,837 Hijacked GitHub Accounts, 8,093 Poisoned Repos and the CI Secrets Audit to Run Today

From 10 November 2026, stolen logins for 4,837 GitHub accounts were used to push a CI secret stealer called wfx into more than 8,000 public repositories. It takes Actions secrets, cloud keys and AI keys, then plants cloud backdoors that survive rotation. Here is how it worked and what to check.

Safeguard Security Team
Updated 37 min read

Starting at about 12:45 UTC on Tuesday 10 November 2026, an attacker used stolen credentials for 4,837 GitHub accounts to push a CI secret stealer, known as "wfx", into repositories those accounts could write to. SafeDep counted 8,093 public repositories that received a malicious commit on their default branch, and Aikido Security, which first reported the campaign, found signs that the payload ran and sent credentials out of at least 1,806 of them. The stealer takes GitHub Actions secrets, the workflow GITHUB_TOKEN, OIDC tokens, cloud credential files and AI provider keys, then uses what it finds to plant persistence in AWS and Azure that survives a normal secret rotation.

If any account in your organization opened a pull request titled docs: dependency audit notes from a branch called wfx-docs on or after 10 November, treat that account as compromised. If a malicious commit landed in a repository with Actions enabled, treat every secret, variable and cloud role that repository's workflows could reach as stolen, even if you cannot see a workflow run. Then go looking for the backdoors, because rotation alone does not remove them.

Key takeaways

  • This is account takeover, not a GitHub bug. The commits were authored as the real users through their GitHub noreply addresses and were unsigned, which OX Security reads as stolen access tokens rather than a platform flaw. How the credentials were stolen across all 4,837 accounts has not been published.
  • The probe pull request is the tell. Every attack began with a pull request, docs: dependency audit notes, from a wfx-docs branch. Aikido counted these in 61,764 repositories, 92% still open.
  • The payload hid itself in seconds. It ran, then a revert commit removed it and the run and logs were deleted. Endor Labs calls this a "sandwich commit". A gap in workflow run numbers is the best sign it ran.
  • Rotation is not enough. The stealer turned GitHub OIDC tokens into cloud sessions, created long lived AWS keys, opened AWS trust policies to "AWS": "*", and added Azure federated credentials named wfx-ci-*.
  • Package consumers have a small, specific exposure. Block Packagist digitalelvis/neuronai-studio 3.14.3 and Go github.com/onuragtas/docker-env v1.0.278 and v1.0.280.
  • It follows October's GhostAction wave. The victims overlap, but Aikido does not attribute both to one actor.

What happened

The short version: an attacker with a very large pile of working GitHub credentials logged in as real developers, checked which repositories each account could push to, and then pushed a self-deleting secret stealer into as many of them as possible.

Aikido published on 12 November. The Hacker News, Endor Labs and OX Security followed the same day. The stealer is one unobfuscated shell script of about 1,500 lines that labels itself "wfx", the prefix it uses on files, branches and cloud objects. OX calls the campaign "wfx-loot".

Unlike a malicious package, it needed nobody to install anything. The payload ran inside the victim's own CI with the victim's secrets in scope. It also reached repositories the hijacked accounts did not own, 10,431 probe pull requests through collaborator or organisation access, including 28 WebdriverIO repositories via the project's bot account. And it went after the cloud. A stolen GITHUB_TOKEN dies with its job; a permanent IAM key minted from an OIDC session does not.

The numbers, and why the sources disagree

Every figure below comes from a named source. They differ because each team measured a different thing at a different moment, and because GitHub search only covers the default branch of public repositories. Private repositories and other branches are not in any of these counts, so every source expects the real totals to be higher.

MeasureFigureSource and what it measured
Hijacked accounts4,837Aikido, accounts that opened probe pull requests
Repositories with a probe PR61,764Aikido
Probe PRs61,879 in 61,734 repositories, from 4,833 accountsSafeDep, 91% still open
Probe PRsover 66,000 (66,200)OX Security, later in the campaign; about 89% open
Probe PRs into repositories the account does not own10,431Aikido
Repositories with a malicious default branch commit8,093SafeDep, 12:46 UTC 10 Nov to 04:36 UTC 12 Nov
Of those, injected into an existing workflow7,309SafeDep, commit ci: refresh build cache
Of those, npm postinstall hook771SafeDep, commit chore: add ci helper
Of those, new workflow file in an empty repository16SafeDep, commit chore: add dependency audit workflow
Repositories with malicious commits, later count8,926Endor Labs update, 21:35 UTC 12 Nov
Repositories where the stealer ran and sent dataat least 1,806Aikido, The Hacker News, Endor Labs, OX Security
Same, alternative countat least 1,835Aikido's post also gives this figure
Hijacked accounts that publish packagesat least 50npm, Go, Packagist, PyPI or crates.io publishers

SafeDep's three method counts add up to 8,096, three more than its total. We use 8,093 because two sources give it. Treat every number here as a floor.

Timeline

All times are UTC and come from the sources named in each row.

Date and timeEventSource
5 Sep 2025Original GhostAction campaign disclosed: 817 repositories, 327 users, 3,325 secretsGitGuardian
31 Aug 2026A new GhostAction wave pushes its workflow to hundreds of repositoriesThe Hacker News, 15 Oct
8 Oct 2026, 13:20 to 13:44Fake security-audit.yml workflow pushed to 27 repositories of the pyxel author's accountStepSecurity
8 Oct 2026, 21:10 to 21:26Same workflow pushed to 318 repositories of a second maintainer's account in 16 minutesStepSecurity
8 to 9 Oct 2026GhostAction October spike, about 1,900 accounts and more than 30,000 repositoriesGitGuardian via The Hacker News
From 9 Oct 2026GitHub starts preventing the malicious GhostAction workflow from runningGitGuardian via The Hacker News
10 Nov 2026, 12:45First wfx probe pull requests openedAikido, Endor Labs
10 Nov 2026, 12:46First malicious default branch commit in SafeDep's dataSafeDep
11 Nov 2026, 06:33First victim disclosure: a leaked GitHub CLI OAuth token was used to open a probe PREndor Labs
11 Nov 2026, 12:11Two chore: add ci helper commits pushed to webdriverio/webdriverio under the project's bot account, past branch protection; flagged by a contributor in about 20 minutesEndor Labs, OX Security
11 Nov 2026, 15:00End of the main probe PR window in Aikido's dataAikido
12 Nov 2026, 02:08 onwardPayload version 5 adds Telegram exfiltration on every runEndor Labs
12 Nov 2026, 04:36End of SafeDep's counting window; activity still ongoingSafeDep
12 Nov 2026Public disclosure by Aikido; analyses from SafeDep, Endor Labs and OX Security; more than 4,100 new probe PRs that dayAikido, OX Security

No takedown had been announced at the time of the Endor Labs update, and none of the sources quotes a statement from GitHub.

Backstory: from GhostAction to wfx

You cannot read wfx properly without GhostAction, because the two share victims and, in some repositories, the same files.

GhostAction first surfaced in September 2025, when GitGuardian found a workflow named "Github Actions Security" (github_actions_security.yml) posting secrets to a remote server: 817 repositories, 327 users, 3,325 secrets.

It came back in 2026. A new wave began on 31 August, and on 8 October StepSecurity documented a version disguised as a security audit. The account of Takashi Kitao, author of the pyxel game engine, pushed security-audit.yml to 27 repositories. Eight hours later the account of Henry Wu, original author of Uber's athenadriver, pushed it to 318 repositories in 16 minutes. The Hacker News reported that Socket counted more than 500 accounts committing the workflow since 7 October. That payload grepped the working tree for 13 credential patterns (AWS, Anthropic, OpenAI, OpenRouter, GitHub, GitLab and more), ran git log -p --all to catch secrets deleted long ago, and sent everything over plain HTTP to 193.32.204[.]199.

GitGuardian later put the October spike at about 1,900 accounts and more than 30,000 repositories, and found some victims had been hit in earlier waves too, a sign of secrets nobody rotated. StepSecurity's theory for initial access was a token from infostealer logs. One October detail matters: in typecho-fans/plugins, runs stalled at action_required because the repository required approval for workflow runs, and no secrets left.

Now the overlap. The Hacker News reports that accounts hit in October were about 1,000 times more likely to be used in November; Aikido says "significantly more likely". In more than 100 repositories, wfx was injected into October's workflow files. Still, Aikido does not "attribute November to the same actor at this stage", citing different infrastructure, tooling and tradecraft, and noting that stolen credentials pass between groups. The defensible reading: the same exposed accounts were abused twice in five weeks, the second time far more capably.

How the accounts were hijacked

The honest answer is that nobody has published the campaign wide initial access route. What the sources do establish:

  • The attacker logged in as real users. Endor Labs describes the attacker inheriting each developer's write access, including access through collaborator and organisation membership. OX Security notes that the commits were authored with the users' real GitHub noreply email addresses and were unsigned.
  • Stolen tokens, not a GitHub vulnerability. OX Security reads the pattern as stolen access tokens. Endor Labs states there was no typosquatting and no dependency confusion involved.
  • At least one victim lost a GitHub CLI token. The first victim disclosure, at 06:33 UTC on 11 November, reported that a leaked GitHub CLI (gh) OAuth token was used to open the probe pull request. Endor Labs points out that these tokens are long lived and carry full repository access. The GitHub CLI manual says gh stores its token in the system credential store by default, but falls back to a plain text file if no store is available, and the --insecure-storage flag forces plain text. A token in a plain text file is exactly what infostealer malware collects.
  • Bot accounts were used too. At WebdriverIO, the commits came from the project's bot account, and Endor Labs reports they went past branch protection. OX Security's structural recommendation is an end to bot accounts bypassing branch protection.
  • The October victims were hit again. That overlap strongly suggests that many credentials exposed in October, or the infostealer logs behind them, were never revoked.

The likely picture, an informed reading rather than a confirmed fact, is a large pool of personal access tokens, gh OAuth tokens and bot tokens gathered from infostealers and earlier leaks, then used by automation. The lesson does not depend on the route: any token that can push to your default branch is a key to every secret your workflows can see.

The attack chain, step by step

This is a conceptual description for defenders. It contains no exploit code.

  1. Account takeover. The attacker signs in with a stolen token and inherits the account's write access.
  2. Probe. It opens a pull request titled docs: dependency audit notes from a wfx-docs branch. This checks write access and can trigger pull request workflows. Aikido says any account that opened one of these is compromised, even if the stealer never followed.
  3. Payload push, in one of three variants.
    • Workflow injection, commit ci: refresh build cache: about 1,500 lines added to an existing workflow.
    • Install hook, commit chore: add ci helper: a hidden .ci-xxxxxx.sh script plus a postinstall entry in package.json. This one also runs on any developer machine that runs npm install on that commit.
    • Throwaway branch, commit chore: add dependency audit workflow: a new .github/workflows/build-audit-xxxxxx.yml on a chore/xxxxxxxx branch.
  4. Execution. The next workflow run, or the next npm install, runs the stealer.
  5. Escalation and persistence. Tokens and credentials are used to reach cloud accounts and leave backdoors.
  6. Exfiltration. A gzipped bundle goes out by HTTP POST to port 8080 on an attacker server, or to Telegram.
  7. Cover up. A ci: restore or ci: cleanup commit reverts the change, and the workflow run and its logs are deleted.

Endor Labs measured steps 3 to 7 at about one minute per repository.

What the injected workflow looks like

Endor Labs describes the injection as two steps added to an existing job. The first, named "Pass runtime", uses actions/github-script@v7 to expose the runner's internal tokens. The second, named "Restore cache j632", sets environment variables including WF_SECRETS: "${{ toJSON(secrets) }}" and WF_VARS: "${{ toJSON(vars) }}" and then runs the stealer.

That toJSON(secrets) expression is the giveaway. Legitimate workflows reference individual secrets by name. A step that serialises the whole secrets context into one variable is almost never something you want, and GitHub's own secure use reference warns that "automatic redaction is not guaranteed" once a secret value is transformed. It is a pattern you can search for across your history today.

What the payload does

Every variant delivers the same script. Endor Labs and Aikido describe four stages. We describe them at the level a defender needs.

Stage 1: collect

  • All GitHub Actions secrets and variables available to the job, the workflow GITHUB_TOKEN, and the OIDC and Actions runtime tokens.
  • Credential files in their usual locations for AWS, Azure, GCP, Docker, Kubernetes, npm, PyPI, SSH, GPG, Vault and Terraform.
  • AI provider and AI tool keys, with OpenAI, Anthropic, Gemini, Hugging Face, Cursor and Copilot named by Endor Labs.
  • Shell histories, .env, .pem, .p12, .jks, .kdbx and kubeconfig files, and in SafeDep's samples also *.tfvars and *.tfstate files and crypto wallets.
  • The memory of the Actions runner process, where secrets sit in clear text while a job runs. Endor Labs notes the version 1 memory scan was broken by a regular expression typo and fixed in version 2.
  • A copy of the repository, its last 100 commits and recent workflow artifacts, per The Hacker News.
  • On self-hosted runners, cloud credentials from instance metadata, Docker container state and systemd units.

Stage 2: escalate

  • Mints GitHub OIDC tokens and exchanges them for AWS, GCP and Azure sessions.
  • On AWS, chains into other roles and creates long lived IAM access keys, for up to two users in version 2.
  • On GCP, impersonates service accounts.
  • On Azure, replays cached Azure CLI refresh tokens for Microsoft Graph, Azure Resource Manager and Key Vault, and lists Key Vaults.
  • Tries the tokens against HashiCorp Vault and Kubernetes APIs.

Stage 3: persist

  • Rewrites an AWS role trust policy so that any AWS account can assume it ("Principal": {"AWS": "*"}).
  • Adds Azure federated identity credentials named wfx-ci-… to app registrations, pointing at the compromised repository. SafeDep says this lets workflows on the main branch sign in as that app with no stored secret, so it keeps working after you rotate everything.
  • Posts fake success commit statuses so branch protection rules that rely on status checks let changes through.

Stage 4: exfiltrate

  • Packs the loot into a gzipped tarball and posts it to /exfil on port 8080 of an attacker server.
  • From 12 November, sends copies to Telegram through five bots (Worker1 to Worker5) using the Bot API sendDocument call. In SafeDep's sample of 200 payload commits, about half used the first server and about a fifth used Telegram only.
  • Alternative channels exist in the code, the Actions cache (wfx- prefix), artifacts (wfx-loot-<run id>) and the job step summary (between <!--WFX-BEGIN--> markers), but every source says they were disabled in this campaign. They switch on with a "private repository" flag, which suggests the tool was built to run in private repositories too.

How the payload changed in three days

VersionShort hashWindow (UTC)RepositoriesWhat changed
v17e98da4910 Nov 13:02 to 15:09695Workflow step injection; memory scan broken by a typo; exfiltration to 18.143.168[.]96
v25d493fb210 Nov 16:32 to 11 Nov 14:537,342Memory scan and Vault fixed; permanent IAM keys for up to 2 users; Azure Key Vault listing; npm postinstall delivery; second server 194.150.166[.]244
v3500cecaa12 Nov 01:131 (test)Telegram as fallback; server 153.75.82[.]159
v4ed88e34212 Nov 01:521 (test)Telegram primary; HTTP callback pointed at a dead local address
v5986335a412 Nov 02:08 onward2,473Telegram copy on every run; all delivery methods; 1,838 repositories already hit by v1 or v2 hit again

Source: Endor Labs. Repositories that rotated secrets after the first wave without removing the attacker's access were robbed again.

Which CI platforms and secrets are at risk

GitHub Actions is the platform that was attacked. The npm postinstall variant widens the blast radius to developer laptops and to any CI system (GitLab CI, Jenkins, Bitbucket Pipelines, Azure Pipelines) that runs npm install on an affected commit. No source reports injection into other CI configuration files, but their credentials on a runner are in scope.

What the stealer can reachHowWhat to do
Repository, environment and organization Actions secretstoJSON(secrets) in the injected stepRotate every secret any workflow in the repository could read
Actions variablestoJSON(vars)Review for anything sensitive stored as a variable and move it
GITHUB_TOKENRunner environment and memoryShort lived, but check what it did; set a read-only default
GitHub OIDC tokensMinted during the runAudit cloud trust; remove persistence
AWS credentialsFiles, OIDC exchange, instance metadata on self-hosted runnersRotate keys; delete keys created since 10 Nov; fix trust policies
Azure credentialsFiles, OIDC exchange, cached CLI refresh tokensRemove wfx-ci-* federated credentials; revoke sessions; rotate app secrets
GCP credentialsFiles, OIDC exchange, service account impersonationReview service account token generation; rotate keys
Vault and KubernetesOIDC replay, kubeconfig filesReview JWT logins and cluster access; rotate tokens
Package registry tokens (npm, PyPI, Packagist, crates.io)Files and secretsRevoke and reissue; check for publishes since 10 Nov
Docker registry, SSH, GPG keysFilesRotate; remove old keys from accounts
AI provider keysSecrets, files, memoryRevoke and reissue; review usage and billing
Source code and historyRepository copy, last 100 commitsAssume private code and any secret ever committed is exposed

Endor Labs makes the point about package tokens directly: stolen publishing tokens could enable a follow on wave of malicious releases. At least 50 of the hijacked accounts publish to npm, Go, Packagist, PyPI or crates.io.

Indicators of compromise

All indicators below were published by Aikido, SafeDep, Endor Labs or OX Security. IP addresses are defanged. Use them for hunting from 10 November 2026 onward, in every repository your accounts can write to, not just the ones you own. SafeDep also publishes the affected repository list and IOC list as CSV files.

TypeIndicatorContext
PR titledocs: dependency audit notesProbe pull request
Branchwfx-docsProbe branch
Branch patternchore/[a-z0-9]{8}Short lived payload branch
Branch patternci-check-[0-9]{4}Attacker branch
Commit messageci: refresh build cacheWorkflow injection, about 1,500 lines
Commit messagechore: add ci helperHidden script plus postinstall hook
Commit messagechore: add dependency audit workflowNew workflow on a chore/ branch
Commit messagechore: initial commitPushed before a new workflow in an empty repository
Commit messageci: restore, ci: cleanupAttacker reverts
File pattern.ci-[a-z0-9]{6}.shHidden helper run by postinstall
File pattern.github/workflows/build-audit-[a-z0-9]{6}.ymlThrowaway workflow
Workflow step names"Pass runtime", "Restore cache j632"Injected steps
Workflow expressiontoJSON(secrets), toJSON(vars)Whole secrets context dumped
Stringb42d70a7aab9140ca19e8249WF_CTOKEN value in injected workflows
SHA-25689695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cfwfx stealer script
Network194.150.166[.]244:8080/exfilExfiltration server
Network18.143.168[.]96:8080/exfilExfiltration server
Network153.75.82[.]159:8080Exfiltration server (v3 onward)
Networkapi.telegram[.]org, sendDocumentTelegram exfiltration from 12 Nov
Telegramchat -1004340163187, bots Worker1 to Worker5Exfiltration chat
Artifact namewfx-loot-<run id>Disabled channel
Cache key prefixwfx-Disabled channel
Marker<!--WFX-BEGIN-->Disabled step summary channel
Azure / Entrafederated credential named wfx-ci-*Persistence
AWStrust policy with "Principal": {"AWS": "*"}Persistence
AWSrole session names wfx-* (SafeDep: wfx-<pid>)OIDC and role sessions
Related GhostAction host193.32.204[.]199October campaign, seen alongside wfx

Because the attacker moved from fixed IP addresses to Telegram mid campaign, OX Security warns that blocking IPs alone is no longer enough.

Affected packages

PackageEcosystemAffected versionsNotesSource
digitalelvis/neuronai-studioPackagist3.14.3composer install alone does not run the stealer, but npm install or npm ci in the package's vendor directory does. Pin 3.14.2 or earlier; OX lists 3.14.4 as fixed. Treat any machine that installed or opened 3.14.3 as compromisedAikido, SafeDep, Endor Labs, OX
github.com/onuragtas/docker-env (also github.com/onuragtas/redock)Gov1.0.278, v1.0.280; SafeDep and OX add v1.0.282Payload is only in a workflow file. Go code and release binaries are unchanged and Go builds never run workflow files, so consumers are likely unaffected. Remove the versions anyway. SafeDep lists clean reverts at v1.0.279, v1.0.281 and v1.0.283; OX lists v1.0.287 as fixedAikido, SafeDep, Endor Labs, OX
github.com/vkuznet/gocurlGopseudo-version v0.0.0-20261111095532-8d5aad0cb0daThe repository has no tags, so @latest resolves to this commit. The payload sits in a workflow that has never run. Block the pseudo-versionSafeDep, The Hacker News, OX
WebdriverIO repositoriesGitHub28 repositories received commits from wdio-botNo package release was affected, but anyone who ran npm install on those commits was exposed, and the bot's token should be treated as stolenSafeDep, The Hacker News

SafeDep scanned 218 npm, Go and Packagist packages tied to affected repositories and found no other published version carrying the payload. PyPI and other registries were not checked, so absence there is not proof.

How to check whether you were affected

Work through these in order. The GitHub checks tell you whether you were touched. The cloud checks tell you whether the attacker left a way back in.

1. Search for probe pull requests across your organization

gh search prs "docs: dependency audit notes" --owner YOUR_ORG \
  --created ">=2026-11-10" --limit 1000 \
  --json repository,author,state,url,createdAt

Every author in the result is a compromised account. Record the pull requests before you close them.

2. Search commit history on every branch

The reverts do not erase the malicious commits from history unless someone force pushed. In each clone:

git fetch --all --prune
git log --all --since=2026-11-10 --format='%h %ad %an %ae %s' \
  | grep -E 'ci: refresh build cache|chore: add ci helper|chore: add dependency audit workflow|chore: initial commit|ci: restore|ci: cleanup'

# Any commit, ever, that introduced a whole-secrets dump or the campaign token
git log --all -S 'toJSON(secrets)' --oneline -- .github/workflows
git log --all -S 'b42d70a7aab9140ca19e8249' --oneline

A hit on toJSON(secrets) that predates 10 November is not proof of wfx, but it is worth understanding. Legitimate workflows rarely need it.

3. Look for leftover branches and install hooks

git ls-remote origin 'refs/heads/wfx-docs' 'refs/heads/chore/*' 'refs/heads/ci-check-*'
grep -rn --include=package.json -E '"postinstall"\s*:\s*".*\.ci-[a-z0-9]{6}\.sh' .
find . -name '.ci-*.sh' -not -path './node_modules/*'

4. Check for gaps in workflow run numbers

Endor Labs and Aikido both say this is the most reliable sign the stealer ran, because the attacker deleted the runs. Run numbers count up per workflow, so list them per workflow and look for missing numbers near an attacker commit.

gh api --paginate "repos/OWNER/REPO/actions/runs?created=>=2026-11-10&per_page=100" \
  -q '.workflow_runs[] | [.workflow_id, .run_number, .created_at, .head_sha] | @tsv' \
  | sort -k1,1n -k2,2n

If run 412 is followed by run 414 within a minute of a ci: refresh build cache commit, assume run 413 was the stealer and that everything that workflow could reach is gone.

5. Read your organization audit log

On GitHub Enterprise Cloud, the organization audit log records the actions that matter here. Useful events include workflows.delete_workflow_run, workflows.created_workflow_run, protected_branch.policy_override, git.push (available through the API, streaming or exports), and personal_access_token.access_granted.

gh api --method GET orgs/YOUR_ORG/audit-log \
  -f phrase='action:workflows.delete_workflow_run created:>=2026-11-10' -f per_page=100
gh api --method GET orgs/YOUR_ORG/audit-log \
  -f phrase='action:protected_branch.policy_override created:>=2026-11-10' -f per_page=100

An unexplained run deletion by an account that opened a probe PR is close to confirmation. Affected users should also read their security log.

6. Hunt AWS persistence

IAM is a global service, so its CloudTrail events are recorded in us-east-1. lookup-events covers the last 90 days of management events. For older or organization wide data, query your CloudTrail lake or log archive instead.

for ev in CreateAccessKey UpdateAssumeRolePolicy AssumeRoleWithWebIdentity; do
  aws cloudtrail lookup-events --region us-east-1 \
    --lookup-attributes AttributeKey=EventName,AttributeValue=$ev \
    --start-time 2026-11-10T00:00:00Z --max-results 50 \
    --query 'Events[].[EventTime,Username,EventName]' --output text
done

# Roles whose trust policy lets any AWS account assume them
for r in $(aws iam list-roles --query 'Roles[].RoleName' --output text); do
  aws iam get-role --role-name "$r" --query 'Role.AssumeRolePolicyDocument' --output json \
    | grep -Eq '"AWS": *"\*"' && echo "OPEN TRUST: $r"
done

SafeDep's guidance is to look for AssumeRoleWithWebIdentity or AssumeRole sessions named wfx-*, followed by CreateAccessKey or UpdateAssumeRolePolicy from those sessions. List the access keys of every IAM user and question any created on or after 10 November. AWS documents what a wildcard principal means in a trust policy, and it is never what a CI role should have.

7. Hunt Azure and Entra persistence

az ad app list --all --query "[].id" -o tsv | while read id; do
  az ad app federated-credential list --id "$id" \
    --query "[?starts_with(name,'wfx-ci')].[name,subject]" -o tsv | sed "s/^/$id /"
done

Microsoft documents how to list and delete federated identity credentials on an app registration. Any wfx-ci-* credential you did not create should be deleted, and the app treated as compromised. Also check the Entra audit log for federated credential additions since 10 November, and revoke refresh tokens for any identity whose Azure CLI cache sat on a runner.

8. Hunt GCP, Vault and Kubernetes access

For GCP, SafeDep suggests looking for GenerateAccessToken and SignJwt calls coming from your GitHub workload identity pool. These calls are recorded in Data Access audit logs for the IAM Service Account Credentials API, which may need to be switched on.

gcloud logging read \
  'protoPayload.methodName:("GenerateAccessToken" OR "SignJwt") AND timestamp>="2026-11-10T00:00:00Z"' \
  --limit 200 --format json

For Vault, look for JWT logins whose issuer is the GitHub OIDC issuer, https://token.actions.githubusercontent.com, from repositories that had attacker commits. For Kubernetes, review API access by any identity whose kubeconfig lived on a runner.

9. Check developer machines

Anyone who ran npm install on an affected commit, in any repository on the 771 postinstall list or the WebdriverIO repositories, or who installed digitalelvis/neuronai-studio 3.14.3 and ran npm install in its vendor directory, should treat that machine as compromised. Rotate every credential that was on it.

If you find a hit: the order of operations

The version 5 re-hits show what happens when the order is wrong. Do it like this:

  1. Cut the attacker's access first. For each compromised account, revoke sessions, personal access tokens, OAuth app grants (including gh), SSH keys and deploy keys, and require a password reset with phishing resistant multi-factor authentication. Do the same for bot accounts.
  2. Remove persistence. Delete wfx-ci-* federated credentials, restore AWS trust policies, and delete IAM access keys created since 10 November. Rotation does not touch any of these.
  3. Rotate everything reachable. Repository, environment and organization secrets, cloud keys, registry tokens, AI provider keys, and anything that was in a file on a self-hosted runner.
  4. Clean GitHub. Close probe pull requests, delete wfx-docs, chore/* and ci-check-* branches, confirm the reverts really removed the payload, and review any commit status checks posted on attacker commits.
  5. Check and republish packages. Look for registry publishes since 10 November. SafeDep advises republishing any version built between the malicious commit and the ci: cleanup commit from a clean commit.
  6. Keep watching. The campaign was ongoing at disclosure. Repeat the probe PR search daily.

Where Safeguard fits

To be plain about it: Safeguard did not detect, discover or report the wfx campaign. Aikido Security first reported it, and SafeDep, Endor Labs and OX Security published the analyses this post relies on. What Safeguard offers is a set of generally available capabilities that shrink the blast radius of a campaign like this and answer "are we exposed?" faster.

  • Asset discovery. Asset discovery catalogs repositories across connected Git providers, assigns owners from CODEOWNERS, and flags ungoverned repositories that exist but are not connected. When a compromised account could write to hundreds of repositories, that inventory is your scoping list.
  • CI/CD and pipeline checks. The CI/CD Supply-Chain Security scanner reads GitHub Actions workflows and GitLab CI files and flags third-party actions not pinned to a full commit SHA, the pull_request_target trigger, untrusted event data placed in run: steps, permissions: write-all, and downloads piped straight into a shell. These checks review workflow files as committed, so pair them with the history and run-number hunting above to catch a payload that sat on the default branch for only seconds.
  • Policies and gates. Policies and gates block, warn or notify when a gate is evaluated, and safeguard ci scan --wait --fail-on-gate <gate> fails a CI job when a gate fails. Gating releases on signing and provenance makes an artifact built outside your reviewed path stand out.
  • Package Firewall. Package Firewall is an install-time proxy for npm and pip that blocks malicious, typosquatted and dependency-confusion packages, with audit, warn, block and quarantine modes and an explicit blocklist. If stolen publishing tokens feed a follow-on wave of malicious npm or PyPI releases, the firewall is where those installs stop. For the Packagist and Go versions above, and for lifecycle hooks added to your own package.json, use the SBOM monitoring below and the code owner review in the eight controls later in this post.
  • SBOM monitoring. With SBOMs for every project and continuous scanning, one query tells you which projects pull digitalelvis/neuronai-studio 3.14.3 or github.com/onuragtas/docker-env v1.0.278, and a policy can block those versions from now on.

Common mistakes

  • Rotating before revoking. Version 5 re-hit 1,838 repositories. New secrets in a repository the attacker can still push to are just the next harvest.
  • Trusting the absence of artifacts. No wfx-loot artifact proves nothing. Look for run-number gaps.
  • Only checking repositories you own. 10,431 probe PRs went to repositories reached through collaborator, organization or bot access.
  • Forgetting the cloud. A wfx-ci-* federated credential keeps working after every GitHub secret is rotated.
  • Blocking IPs and stopping there. From 12 November the data also went to Telegram over HTTPS.

Open questions

How the 4,837 sets of credentials were obtained, how many private repositories ran the stealer, and whether stolen publishing tokens will feed malicious releases are all still unanswered.

FAQ

What is the wfx campaign?

A CI secret stealing campaign that began on 10 November 2026. An attacker used stolen credentials for 4,837 GitHub accounts to push a shell script stealer into repositories those accounts could write to. SafeDep counted 8,093 public repositories with a malicious default branch commit.

How do I know if my organization was hit?

Search for pull requests titled docs: dependency audit notes from wfx-docs branches since 10 November, search history for the commit messages listed above, and look for gaps in workflow run numbers. A probe pull request means the account is compromised.

Is it a GitHub vulnerability?

No source reports one. The commits were made with stolen user tokens, as the real users, and unsigned.

Which secrets should I rotate?

Every repository, environment and organization secret and variable that a workflow in an affected repository could read, plus cloud keys, registry tokens and AI provider keys. Remove the cloud backdoors and cut the attacker's access first.

Does rotating secrets remove the attacker?

No. AWS keys created by the attacker, rewritten trust policies and wfx-ci-* Azure federated credentials survive rotation.

Is GitHub OIDC unsafe now?

No, but OIDC trust scoped only to a repository or to main gives malicious code on main the same cloud access. Bind deploy roles to protected environments with required reviewers.

I only consume packages. Am I affected?

Only if you installed digitalelvis/neuronai-studio 3.14.3 and ran npm install in its vendor directory, or cloned an affected repository and ran npm install on a poisoned commit. The Go versions carry the payload only in a workflow file. Block them anyway.

Is wfx the same group as GhostAction?

Not established. Victims and files overlap, but Aikido cites different infrastructure, tooling and tradecraft.

Did Safeguard detect this campaign?

No. The research credit belongs to Aikido Security, SafeDep, Endor Labs and OX Security.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Griffin, Safeguard's security model, opens a policy gated pull request that moves the affected dependency to the safe version, and your own CI runs your tests on it before anyone merges. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Auto-merge is a separate switch and stays off by default, so your team decides what merges on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Safeguard Gold rebuilds patched versions of open-source packages and images from source, signs each build, attaches an SBOM, SLSA provenance and VEX, and serves them from registry.safeguard.sh to your organization. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

4. For wfx: eight controls you can change today

None of these depend on a maintainer, a vendor or a release. GitHub's secure use reference covers most of them.

  1. Rotate secrets, in the right order. Revoke the attacker's access, remove cloud persistence, then rotate every secret, variable, cloud key, registry token and AI key the affected workflows could reach. Rotate anything ever committed to history too.
  2. Pin actions to full commit SHAs. The injected step used actions/github-script@v7, a tag. GitHub says a tag "can be moved or deleted" by anyone who controls the action's repository. Organization admins can now require SHA pinning, and workflows using unpinned actions fail.
  3. Make GITHUB_TOKEN least privilege. Set the organization default to read-only and grant each job only the permissions: it needs. GitHub says the token expires when the job finishes, so a narrow token stolen mid run is worth little.
  4. Use OIDC for short-lived cloud credentials, scoped tightly. OIDC means the cloud issues "a short-lived access token that is only valid for a single job", so no long-lived keys sit in secrets. wfx abused OIDC from inside trusted workflows, so bind roles to a protected environment (repo:org/repo:environment:production) with required reviewers.
  5. Protect branches with no bypass. Block direct pushes to default branches for admins and bots. Use rulesets that require status checks from a specific GitHub App, so a fake success status posted with a stolen token does not count.
  6. Require reviews on workflow files. Put .github/workflows/** under CODEOWNERS with code owner review required. Alert on new lifecycle hooks in package.json and use npm ci --ignore-scripts where builds allow.
  7. Control egress on runners. The stealer posted to raw IPs on port 8080, then to Telegram. On self-hosted runners in your own network, allow only the destinations builds need. Keep self-hosted runners off public repositories.
  8. Audit organization logs. Stream the GitHub audit log and alert on workflows.delete_workflow_run, protected_branch.policy_override and new token grants. In the cloud, alert on CreateAccessKey, UpdateAssumeRolePolicy and new federated credentials.

See how Safeguard would have helped

These demos show general Safeguard workflows, not this incident. Each note says how the same flow applies to wfx.

The first demo is a Safeguard CLI session fixing a lodash CVE: it reads the finding, asks before any command that changes something, edits package.json, runs the install and tests with your approval and pushes nothing. For wfx you would make the same kind of request in a cleaned repository: pin a workflow's actions to commit SHAs, add a permissions: block, and review the diff before you commit.

Click through it at your own pace, or open it full screen.

The second demo sets up the Safeguard local runner, approves it with a device code, and scans from the terminal. During an incident it lets you scan each affected repository and its lock file on a machine you trust.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose the tools Claude may use. Once connected, you can ask an assistant which projects carry digitalelvis/neuronai-studio 3.14.3 while you plan the cleanup. What it can answer depends on what your workspace has scanned, so confirm against the sources below.

Click through it at your own pace, or open it full screen.

To try it on your own repositories, start at https://safeguard.sh, download Safeguard or read the CLI documentation.

Quick check

A probe PR from one of your developers is open, but no malicious commit appears in that repository. Is the account safe?

No. Aikido treats any account that opened a probe PR as compromised. Revoke its tokens, sessions, OAuth grants and SSH keys, and check every repository it can write to.

You rotated every Actions secret on 11 November. Are you done?

Not unless you first cut the attacker's access and removed cloud persistence. Version 5 re-hit 1,838 repositories, and wfx-ci-* credentials and wildcard AWS trust policies survive rotation.

Your deploy role trusts OIDC tokens from your repository's main branch. Is that enough?

Not here. Malicious code pushed to main mints tokens that match that trust. Bind the role to a protected environment with required reviewers.

Sources

Related reading on Safeguard: GhostAction returns, Megalodon mass workflow injection, set a read-only GITHUB_TOKEN default, replace long-lived cloud secrets with OIDC and map your Actions secrets blast radius.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

We email you a link to confirm, and only then add you to the list. Unsubscribe in one click. Privacy

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.