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 awfx-docsbranch. 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 namedwfx-ci-*. - Package consumers have a small, specific exposure. Block Packagist
digitalelvis/neuronai-studio3.14.3 and Gogithub.com/onuragtas/docker-envv1.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.
| Measure | Figure | Source and what it measured |
|---|---|---|
| Hijacked accounts | 4,837 | Aikido, accounts that opened probe pull requests |
| Repositories with a probe PR | 61,764 | Aikido |
| Probe PRs | 61,879 in 61,734 repositories, from 4,833 accounts | SafeDep, 91% still open |
| Probe PRs | over 66,000 (66,200) | OX Security, later in the campaign; about 89% open |
| Probe PRs into repositories the account does not own | 10,431 | Aikido |
| Repositories with a malicious default branch commit | 8,093 | SafeDep, 12:46 UTC 10 Nov to 04:36 UTC 12 Nov |
| Of those, injected into an existing workflow | 7,309 | SafeDep, commit ci: refresh build cache |
Of those, npm postinstall hook | 771 | SafeDep, commit chore: add ci helper |
| Of those, new workflow file in an empty repository | 16 | SafeDep, commit chore: add dependency audit workflow |
| Repositories with malicious commits, later count | 8,926 | Endor Labs update, 21:35 UTC 12 Nov |
| Repositories where the stealer ran and sent data | at least 1,806 | Aikido, The Hacker News, Endor Labs, OX Security |
| Same, alternative count | at least 1,835 | Aikido's post also gives this figure |
| Hijacked accounts that publish packages | at least 50 | npm, 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 time | Event | Source |
|---|---|---|
| 5 Sep 2025 | Original GhostAction campaign disclosed: 817 repositories, 327 users, 3,325 secrets | GitGuardian |
| 31 Aug 2026 | A new GhostAction wave pushes its workflow to hundreds of repositories | The Hacker News, 15 Oct |
| 8 Oct 2026, 13:20 to 13:44 | Fake security-audit.yml workflow pushed to 27 repositories of the pyxel author's account | StepSecurity |
| 8 Oct 2026, 21:10 to 21:26 | Same workflow pushed to 318 repositories of a second maintainer's account in 16 minutes | StepSecurity |
| 8 to 9 Oct 2026 | GhostAction October spike, about 1,900 accounts and more than 30,000 repositories | GitGuardian via The Hacker News |
| From 9 Oct 2026 | GitHub starts preventing the malicious GhostAction workflow from running | GitGuardian via The Hacker News |
| 10 Nov 2026, 12:45 | First wfx probe pull requests opened | Aikido, Endor Labs |
| 10 Nov 2026, 12:46 | First malicious default branch commit in SafeDep's data | SafeDep |
| 11 Nov 2026, 06:33 | First victim disclosure: a leaked GitHub CLI OAuth token was used to open a probe PR | Endor Labs |
| 11 Nov 2026, 12:11 | Two 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 minutes | Endor Labs, OX Security |
| 11 Nov 2026, 15:00 | End of the main probe PR window in Aikido's data | Aikido |
| 12 Nov 2026, 02:08 onward | Payload version 5 adds Telegram exfiltration on every run | Endor Labs |
| 12 Nov 2026, 04:36 | End of SafeDep's counting window; activity still ongoing | SafeDep |
| 12 Nov 2026 | Public disclosure by Aikido; analyses from SafeDep, Endor Labs and OX Security; more than 4,100 new probe PRs that day | Aikido, 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 saysghstores 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-storageflag 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.
- Account takeover. The attacker signs in with a stolen token and inherits the account's write access.
- Probe. It opens a pull request titled
docs: dependency audit notesfrom awfx-docsbranch. 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. - 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.shscript plus apostinstallentry inpackage.json. This one also runs on any developer machine that runsnpm installon that commit. - Throwaway branch, commit
chore: add dependency audit workflow: a new.github/workflows/build-audit-xxxxxx.ymlon achore/xxxxxxxxbranch.
- Workflow injection, commit
- Execution. The next workflow run, or the next
npm install, runs the stealer. - Escalation and persistence. Tokens and credentials are used to reach cloud accounts and leave backdoors.
- Exfiltration. A gzipped bundle goes out by HTTP POST to port 8080 on an attacker server, or to Telegram.
- Cover up. A
ci: restoreorci: cleanupcommit 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,.kdbxand kubeconfig files, and in SafeDep's samples also*.tfvarsand*.tfstatefiles 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 themainbranch sign in as that app with no stored secret, so it keeps working after you rotate everything. - Posts fake
successcommit 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
/exfilon port 8080 of an attacker server. - From 12 November, sends copies to Telegram through five bots (Worker1 to Worker5) using the Bot API
sendDocumentcall. 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
| Version | Short hash | Window (UTC) | Repositories | What changed |
|---|---|---|---|---|
| v1 | 7e98da49 | 10 Nov 13:02 to 15:09 | 695 | Workflow step injection; memory scan broken by a typo; exfiltration to 18.143.168[.]96 |
| v2 | 5d493fb2 | 10 Nov 16:32 to 11 Nov 14:53 | 7,342 | Memory 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 |
| v3 | 500cecaa | 12 Nov 01:13 | 1 (test) | Telegram as fallback; server 153.75.82[.]159 |
| v4 | ed88e342 | 12 Nov 01:52 | 1 (test) | Telegram primary; HTTP callback pointed at a dead local address |
| v5 | 986335a4 | 12 Nov 02:08 onward | 2,473 | Telegram 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 reach | How | What to do |
|---|---|---|
| Repository, environment and organization Actions secrets | toJSON(secrets) in the injected step | Rotate every secret any workflow in the repository could read |
| Actions variables | toJSON(vars) | Review for anything sensitive stored as a variable and move it |
GITHUB_TOKEN | Runner environment and memory | Short lived, but check what it did; set a read-only default |
| GitHub OIDC tokens | Minted during the run | Audit cloud trust; remove persistence |
| AWS credentials | Files, OIDC exchange, instance metadata on self-hosted runners | Rotate keys; delete keys created since 10 Nov; fix trust policies |
| Azure credentials | Files, OIDC exchange, cached CLI refresh tokens | Remove wfx-ci-* federated credentials; revoke sessions; rotate app secrets |
| GCP credentials | Files, OIDC exchange, service account impersonation | Review service account token generation; rotate keys |
| Vault and Kubernetes | OIDC replay, kubeconfig files | Review JWT logins and cluster access; rotate tokens |
| Package registry tokens (npm, PyPI, Packagist, crates.io) | Files and secrets | Revoke and reissue; check for publishes since 10 Nov |
| Docker registry, SSH, GPG keys | Files | Rotate; remove old keys from accounts |
| AI provider keys | Secrets, files, memory | Revoke and reissue; review usage and billing |
| Source code and history | Repository copy, last 100 commits | Assume 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.
| Type | Indicator | Context |
|---|---|---|
| PR title | docs: dependency audit notes | Probe pull request |
| Branch | wfx-docs | Probe branch |
| Branch pattern | chore/[a-z0-9]{8} | Short lived payload branch |
| Branch pattern | ci-check-[0-9]{4} | Attacker branch |
| Commit message | ci: refresh build cache | Workflow injection, about 1,500 lines |
| Commit message | chore: add ci helper | Hidden script plus postinstall hook |
| Commit message | chore: add dependency audit workflow | New workflow on a chore/ branch |
| Commit message | chore: initial commit | Pushed before a new workflow in an empty repository |
| Commit message | ci: restore, ci: cleanup | Attacker reverts |
| File pattern | .ci-[a-z0-9]{6}.sh | Hidden helper run by postinstall |
| File pattern | .github/workflows/build-audit-[a-z0-9]{6}.yml | Throwaway workflow |
| Workflow step names | "Pass runtime", "Restore cache j632" | Injected steps |
| Workflow expression | toJSON(secrets), toJSON(vars) | Whole secrets context dumped |
| String | b42d70a7aab9140ca19e8249 | WF_CTOKEN value in injected workflows |
| SHA-256 | 89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf | wfx stealer script |
| Network | 194.150.166[.]244:8080/exfil | Exfiltration server |
| Network | 18.143.168[.]96:8080/exfil | Exfiltration server |
| Network | 153.75.82[.]159:8080 | Exfiltration server (v3 onward) |
| Network | api.telegram[.]org, sendDocument | Telegram exfiltration from 12 Nov |
| Telegram | chat -1004340163187, bots Worker1 to Worker5 | Exfiltration chat |
| Artifact name | wfx-loot-<run id> | Disabled channel |
| Cache key prefix | wfx- | Disabled channel |
| Marker | <!--WFX-BEGIN--> | Disabled step summary channel |
| Azure / Entra | federated credential named wfx-ci-* | Persistence |
| AWS | trust policy with "Principal": {"AWS": "*"} | Persistence |
| AWS | role session names wfx-* (SafeDep: wfx-<pid>) | OIDC and role sessions |
| Related GhostAction host | 193.32.204[.]199 | October 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
| Package | Ecosystem | Affected versions | Notes | Source |
|---|---|---|---|---|
digitalelvis/neuronai-studio | Packagist | 3.14.3 | composer 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 compromised | Aikido, SafeDep, Endor Labs, OX |
github.com/onuragtas/docker-env (also github.com/onuragtas/redock) | Go | v1.0.278, v1.0.280; SafeDep and OX add v1.0.282 | Payload 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 fixed | Aikido, SafeDep, Endor Labs, OX |
github.com/vkuznet/gocurl | Go | pseudo-version v0.0.0-20261111095532-8d5aad0cb0da | The repository has no tags, so @latest resolves to this commit. The payload sits in a workflow that has never run. Block the pseudo-version | SafeDep, The Hacker News, OX |
| WebdriverIO repositories | GitHub | 28 repositories received commits from wdio-bot | No package release was affected, but anyone who ran npm install on those commits was exposed, and the bot's token should be treated as stolen | SafeDep, 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:
- 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. - 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. - 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.
- Clean GitHub. Close probe pull requests, delete
wfx-docs,chore/*andci-check-*branches, confirm the reverts really removed the payload, and review any commit status checks posted on attacker commits. - Check and republish packages. Look for registry publishes since 10 November. SafeDep advises republishing any version built between the malicious commit and the
ci: cleanupcommit from a clean commit. - 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_targettrigger, untrusted event data placed inrun: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-studio3.14.3 orgithub.com/onuragtas/docker-envv1.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-lootartifact 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 situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull 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.
- 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.
- 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. - Make
GITHUB_TOKENleast privilege. Set the organization default to read-only and grant each job only thepermissions:it needs. GitHub says the token expires when the job finishes, so a narrow token stolen mid run is worth little. - 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. - 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
successstatus posted with a stolen token does not count. - Require reviews on workflow files. Put
.github/workflows/**under CODEOWNERS with code owner review required. Alert on new lifecycle hooks inpackage.jsonand usenpm ci --ignore-scriptswhere builds allow. - 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.
- Audit organization logs. Stream the GitHub audit log and alert on
workflows.delete_workflow_run,protected_branch.policy_overrideand new token grants. In the cloud, alert onCreateAccessKey,UpdateAssumeRolePolicyand 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
- Aikido Security: wfx-loot campaign pushes CI secret stealer to tens of thousands of repositories (12 Nov 2026)
- SafeDep: wfx, GitHub Actions credential stealer hits 8,000 repos (12 Nov 2026), with the affected repositories CSV and IOC CSV
- Endor Labs: GitHub Actions supply chain attack targeting CI secrets at scale (12 Nov 2026, updated 21:35 UTC)
- OX Security: Thousands of GitHub accounts hijacked with wfx-loot CI secret stealing malware (12 Nov 2026)
- The Hacker News: attacker used 4,837 hijacked GitHub accounts to run a CI secret stealer (12 Nov 2026)
- StepSecurity: GhostAction returns (9 Oct 2026)
- The Hacker News: credential stealing GitHub Actions workflows (9 Oct 2026, updated 15 Oct)
- The Hacker News: GhostAction compromises about 1,900 GitHub accounts (15 Oct 2026)
- GitGuardian: GhostAction campaign, 3,325 secrets stolen (5 Sep 2025)
- GitHub Docs: secure use reference for GitHub Actions
- GitHub Docs: GITHUB_TOKEN
- GitHub Docs: security hardening with OpenID Connect
- GitHub Changelog: Actions policy supports blocking and SHA pinning (15 Aug 2025)
- GitHub Docs: about rulesets and about code owners
- GitHub Docs: reviewing the audit log for your organization and audit log events
- GitHub CLI manual: gh auth login
- AWS IAM: policy element Principal
- Microsoft Learn: create a trust relationship with an external identity provider
- npm Docs: ignore-scripts
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.