wfx-loot GitHub Actions Campaign

The wfx-loot campaign (also known as WFX Phantom) has highlighted over 63,000 targeted GitHub repositories. While the footprint is broad, security teams need to separate automated reconnaissance from actual payload execution. wfx-loot is not a self-propagating open-source worm; it is a fast, clean-up-aware CI/CD secret harvester built to raid GitHub Actions runners and plant persistent backdoors in AWS and Microsoft Entra ID.

Drawing on primary telemetry published today by Aikido Security, StepSecurity, and SafeDep, this advisory breaks down what actually executed, how the operator switched up their tradecraft, and how to hunt for evidence within your own environment, even after the workflow logs have been wiped.

1. Probing Volume vs Actual Blast Radius

Starting around 12:45 UTC on 10 November 2026, an operator fed 4,837 stolen GitHub developer and bot tokens into an automated pipeline. Aikido Security identified a substantial victim overlap with October's GhostAction campaign, suggesting that this has resulted in shared or resold credential dumps rather than a Github platform vulnerability.

  • Write-access probes: Over 63,000 pull requests titled "docs: dependency audit notes" opened from wfx-docs branches, including 10,431 across external organisations via collaborator access (92% remain open).

  • Confirmed secret theft: Roughly 8,093 public repositories received follow-on payload commits, with Aikido confirming execution and secret exfiltration in at least 1,835 repositories.

  • Publisher token risk: At least 50 compromised accounts maintain packages across npm, PyPI, Go, Packagist, and crates.io, leaving secondary registry poisoning a live follow-on risk.

Affected Open-Source Packages

Despite concerns over widespread registry poisoning, only three published packages absorbed the payload through automated tagging or proxy caching:

  • Packagist release: In digitalelvis/neuronai-studio v3.14.3, running composer install does not execute the script; it triggers only if npm install or npm ci runs manually inside its vendor folder.

  • Go modules: In github.com/vkuznet/gocurl (commit 8d5aad0) and github.com/onuragtas/redock (v1.0.278, v1.0.280, v1.0.282), edits touched only workflow YAML files, which go get and go build ignore.

2. Four Hiding Places, Memory Scraping, and Today's Telegram Pivot

Once write access is confirmed—even if the probe pull request is never merged—the operator pushes a heavily commented, un-obfuscated 1,500-line shell script (self-designated as wfx) using one of four delivery patterns:

1. Workflow YAML injection: Commits titled ci: refresh build cache (7,309 repos) inject steps dumping secrets and variables into workflow YAML files and add statuses: write permissions to forge passing checks.

2. CI helper scripts: Variants of ci: refresh build cache append the stealer directly to existing shell scripts called by workflows, such as update.sh or test runner scripts.

3. Throwaway workflow branches: Commits titled chore: add dependency audit workflow push a rogue build-audit workflow file onto short-lived chore/<8-chars> branches (or empty repos after an initial commit).

4. npm lifecycle hooks: Commits titled chore: add ci helper (771 repos) drop a hidden .ci-<6-chars>.sh script and hook postinstall in package.json, exposing developer laptops if npm install runs.

Why Unreferenced Secrets Still Leak

Even when appended to a secondary shell script or npm hook that never references GitHub secrets in YAML, wfx uses python3 (with sudo where available) to scrape process memory across Runner.Worker and Runner.Listener processes. That extracts decrypted secrets held in runner memory alongside environment files, cloud CLI profiles, SSH keys, Kubernetes configs, AI coding agent keys (OpenAI, Anthropic, Gemini, Cursor, Copilot, Codex), and self-hosted runner cloud metadata (IMDS).

Shift to Telegram-Only Exfiltration

Initial waves posted gzipped tarballs over HTTP to 194.150.166[.]244:8080/exfil, 18.143.168[.]96:8080/exfil, and 153.75.82[.]159:8080. StepSecurity's analysis of commits pushed from 05:30 UTC on 12 November shows that the operator stripped hardcoded C2 IPs, pointed the HTTP callback variable to 127.0.0.1, and shifted exfiltration exclusively to Telegram's Bot API (api.telegram.org, chat -1004340163187). The script also contains dormant flags to exfiltrate via GitHub Actions Cache, Artifacts, and Step Summaries in restricted private repositories.

60-Second Anti-Forensics

Immediately after execution, the operator pushes a revert commit ("ci: restore" or "ci: cleanup") and calls the GitHub API to delete the workflow run and its logs, often within 12 to 60 seconds. Inspecting the current branch diff or checking for failed Actions builds will show nothing unusual. Missing run numbers in the sequential Actions history (for instance, jumping straight from #1963 to #1965) are the clearest UI indicator that the payload ran.

3. Why Rotating GitHub Secrets Does Not Evict the Attacker

Where an affected workflow held cloud permissions (id-token: write for OIDC or stored cloud credentials), wfx attempts to establish access designed to outlast secret rotation:

  • AWS IAM backdoors: Exchanges OIDC tokens for wfx- sessions, mints IAM user access keys via iam:CreateAccessKey, and rewrites role trust policies (iam:UpdateAssumeRolePolicy) to Principal "*".

  • Entra ID persistence: Replays cached refresh tokens (including FOCI family tokens) against Microsoft Graph to register wfx-ci-* federated identity credentials on app registrations for secretless Azure access.

4. Threat Hunting Pack

A. GitHub Estate Triage (Search & Audit Logs)

Make sure to wrap search phrases in quotes and include branch qualifiers to avoid matches of individual words across millions of routine Dependabot pull requests.

1. Identify probe PRs (confirms the opening user or bot account is compromised)

org: is:pr "docs: dependency audit notes" head:wfx-docs

2. Identify payload delivery and revert commits across repositories

org: ("ci: refresh build cache" OR "chore: add ci helper" OR "chore: add dependency audit workflow" OR "ci: restore" OR "ci: cleanup")

3. GitHub Enterprise Audit Log: hunt deleted workflow runs

action:workflows.delete_workflow_run created:>=2026-11-10

B. Spot Missing Sequential Actions Runs (GitHub CLI + jq)

Flags gaps in sequential workflow run numbers where a run was deleted after exfiltration:


gh api "repos///actions/runs?per_page=100"
--jq '.workflow_runs | group_by(.workflow_id)[] | {workflow: .[0].name, missing_runs: (([.[].run_number] | sort) as $r | [range($r[0]; $r[-1]+1)] - $r)} | select(.missing_runs | length > 0)'

C. AWS CloudTrail (Athena / CloudTrail Lake SQL)


Detects wfx- OIDC/STS sessions, wildcard trust-policy rewrites, and assumed roles minting IAM user access keys:

sql

SELECT eventTime, eventName, userIdentity.arn, sourceIPAddress, requestParameters

FROM cloudtrail_logs

WHERE eventTime >= '2026-11-10T00:00:00Z'

AND (

requestParameters LIKE '%wfx-%'

OR (eventName = 'UpdateAssumeRolePolicy' AND requestParameters LIKE '%"*"%')

OR (eventName = 'CreateAccessKey' AND userIdentity.type = 'AssumedRole')

) SELECT eventTime, eventName, userIdentity.arn, sourceIPAddress, requestParameters

FROM cloudtrail_logs

WHERE eventTime >= '2026-11-10T00:00:00Z'

AND (

requestParameters LIKE '%wfx-%'

OR (eventName = 'UpdateAssumeRolePolicy' AND requestParameters LIKE '%"*"%')

OR (eventName = 'CreateAccessKey' AND userIdentity.type = 'AssumedRole')

)


D. Microsoft Entra ID / Sentinel (KQL)

Detects wfx-ci- federated identity credentials added to Entra application registrations:

AuditLogs

| where TimeGenerated >= datetime(2026-11-10)

| where Category == "ApplicationManagement"

| where OperationName has_any ("Update application", "Add federated identity credential")

| where tostring(TargetResources) has "wfx-ci-"

| project TimeGenerated, OperationName, InitiatedBy, TargetResources


E. Self-Hosted Runners & Developer Workstations (Defender MDE / Sentinel KQL)

Catches Telegram Bot API uploads from CLI tools, legacy HTTP C2 IPs, runner process memory scraping, and hidden npm hook scripts:

union DeviceNetworkEvents, DeviceProcessEvents

| where Timestamp >= datetime(2026-11-10)

| where (RemoteUrl has "api.telegram.org" and InitiatingProcessFileName in~ ("curl", "wget", "python3", "sh", "bash"))

or RemoteIP in ("194.150.166.244", "18.143.168.96", "153.75.82.159")

or (FileName in~ ("python", "python3") and ProcessCommandLine has "/proc/" and ProcessCommandLine has "/mem")

or ProcessCommandLine matches regex @".ci-[a-z0-9]{6}.sh"

or ProcessCommandLine has "-1004340163187"

| project Timestamp, DeviceName, ActionType, FileName, ProcessCommandLine, RemoteIP, RemoteUrl

5. Remediation Sequence

If you uncover a suspected probe pull request, delivery commit, or deleted Actions run - assume complete compromise:

1. Revoke GitHub identities: Revoke all PATs, OAuth grants, SSH keys, and sessions for any user or release bot (such as wdio-bot) that opened a probe PR or pushed a payload commit, and enforce phishing-resistant MFA.

2. Evict cloud persistence: Revert wildcard ("*") AWS IAM role trust policies, delete rogue IAM access keys, remove Entra wfx-ci-* federated credentials, and audit GCP Workload Identity and HashiCorp Vault OIDC logs.

3. Rotate exposed secrets: Rotate all repository, environment, and accessible organisation secrets alongside cloud keys, package-registry tokens, and AI API keys; rebuild any affected self-hosted runners.

4. Audit developer endpoints: Isolate and rotate credentials on any workstation that ran npm install against a poisoned commit, and block the affected Packagist (neuronai-studio v3.14.3) and Go module versions.

Next
Next

PHP2Shell Shows Why The Time To Patch Is Shrinking