Threat reportSupply ChainTL-2026-5982
wfx-loot: GitHub Actions CI secret stealer pushed to public repositories using stolen GitHub credentials
A mass wave (precursor Oct 24-29, 2026; main wave from Nov 10, still running Nov 12) used stolen GitHub credentials to push the wfx-loot stealer into public repositories via new workflows, modified workflows, npm lifecycle hooks or appended shell scripts.
Key facts
| Identifiers | TL-2026-5982 |
|---|---|
| Also known as | wfx-loot |
| Severity | HIGH |
| First disclosed | |
| Affected products | GitHub GitHub Actions workflows and public repositories whose account credentials were stolen; npm npm/pnpm lifecycle scripts (postinstall, build) in affected repositories |
| Weaknesses (CWE) | CWE-506 |
| Malware | wfx-loot |
| MITRE ATT&CK techniques | T1003.007 Proc Filesystem, T1059.004 Unix Shell, T1071.001 Web Protocols, T1078 Valid Accounts, T1098.001 Additional Cloud Credentials, T1119 Automated Collection, T1195.002 Compromise Software Supply Chain, T1213.003 Code Repositories, T1528 Steal Application Access Token, T1546 Event Triggered Execution, T1552.001 Credentials In Files, T1552.004 Private Keys and 3 more in the attack flow |
| Motivation | UNKNOWN |
| Target sectors | technology, software development, open source, education |
| Target regions | Global |
| Detection rules | 9 (1 shown free) |
| Sources | 7 cited sources |
Detection coverage
As of 2026-11-13, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-5982 across Splunk SPL, Microsoft KQL and Sigma, covering 17 indicator(s) of compromise. The whole corpus is readable without an account. Every threat comes with one complete detection in Splunk SPL, Microsoft KQL and Sigma, free for everyone; a free account adds daily TL Creds to unlock the rest. Paid plans add a larger daily allowance, correlation and the MCP server. Threadlinqs MCP server · View plans.
Free detection: wfx-loot CI Payload Push - Throwaway Workflow, Hidden Hook Script, Probe PR Branch and Decoy Commit Messages (Exploitation)
Detects the push of wfx-loot CI secret stealer content into a repository by an account using stolen GitHub credentials: a .github/workflows/build-audit-xxxxxx.yml workflow, a hidden .ci-xxxxxx.sh script at the repository root, the docs/wfx-audit.md probe file or wfx-docs branch, a chore/xxxxxxxx branch, and the campaign's decoy commit messages ('ci: refresh build cache', 'ci: restore', 'chore: add ci helper'). A weighted score keeps single weak signals (a common message alone) from alerting. Also available as KQL (GitHub audit log) and Sigma.
- Languages: Splunk SPL, Microsoft KQL, Sigma
- ATT&CK: T1195.002, T1078, T1564
- Data sources: github:webhook:push (Splunk index=github), GitHubAuditLogPolling_CL, github audit log
- Severity: critical
- Confidence: high
Splunk SPL
index=github sourcetype="github:webhook:push" earliest=-24h
| eval added=mvappend('head_commit.added{}','commits{}.added{}'), msg=lower('head_commit.message')
| eval wf_form=if(mvcount(mvfilter(match(added,"^[.]github/workflows/build-audit-[a-z0-9]{6}[.]yml$")))>0,1,0)
| eval hook_form=if(mvcount(mvfilter(match(added,"^[.]ci-[a-z0-9]{6}[.]sh$")))>0,1,0)
| eval probe_form=if(mvcount(mvfilter(match(added,"^docs/wfx-audit[.]md$")))>0 OR ref="refs/heads/wfx-docs",1,0)
| eval branch_form=if(match(ref,"^refs/heads/chore/[a-z0-9]{8}$"),1,0)
| eval msg_form=if(match(msg,"^(ci: refresh build cache|ci: restore|ci: cleanup|chore: add ci helper|chore: add dependency audit workflow|ci: cache)$"),1,0)
| eval score=wf_form*2+hook_form*2+probe_form*2+branch_form+msg_form
| where score>=2
| stats count min(_time) as first_seen max(_time) as last_seen values(added) as added_files values(msg) as commit_messages values(ref) as refs max(score) as max_score by repository.full_name, pusher.name, sender.login
| convert ctime(first_seen) ctime(last_seen)Microsoft KQL
GitHubAuditLogPolling_CL
| where TimeGenerated > ago(24h)
| where action_s == "workflows.created_workflow_run"
| where head_branch_s matches regex @"^chore/[a-z0-9]{8}$"
or name_s matches regex @"^Dependency Audit [a-z0-9]{4}$"
or head_branch_s == "wfx-docs"
| project TimeGenerated, actor_s, org_s, repo_s, name_s, head_branch_s, event_s, workflow_run_id_d
| order by TimeGenerated descSigma
title: wfx-loot Throwaway Dependency Audit Workflow Run on chore Branch
id: 5c1e7a52-3b0d-4f0a-9a43-6d1f2b8e0a11
status: experimental
description: Detects GitHub Actions workflow runs created by the wfx-loot CI secret stealer - a throwaway workflow named 'Dependency Audit xxxx' (file build-audit-xxxxxx.yml) running from a chore/xxxxxxxx branch, or the wfx-docs probe branch.
references:
- https://blog.gitguardian.com/wfx-loot-a-ci-secret-stealer-pushed-with-stolen-github-credentials/
author: Threadlinqs Team
date: 2026/11/13
tags:
- attack.initial_access
- attack.t1195.002
- attack.t1078
- attack.defense_evasion
- attack.t1564
logsource:
product: github
service: audit
detection:
selection_action:
action: 'workflows.created_workflow_run'
selection_name:
name|re: '^Dependency Audit [a-z0-9]{4}$'
selection_branch:
head_branch|re: '^chore/[a-z0-9]{8}$'
selection_probe:
head_branch: 'wfx-docs'
condition: selection_action and (selection_name or selection_branch or selection_probe)
falsepositives:
- Legitimate bots that name branches chore/xxxxxxxx and run a workflow titled Dependency Audit
level: high
Known false positives:
- Developers legitimately adding a .ci-*.sh helper script or a workflow named build-audit-*.yml
- Automation bots that commit 'ci: cache' or 'ci: cleanup' to chore/* branches
Tuning notes: Raise score threshold to 3 if the decoy-message and branch signals alone are noisy; allow-list known automation accounts via sender.login. Webhook field names assume the standard GitHub push payload ingested as JSON; adjust to your HEC mapping. For the KQL form confirm the GitHubAuditLogPolling_CL column names (head_branch_s, name_s) in your workspace.
- wfx-loot npm Lifecycle Hook Executing Hidden .ci-xxxxxx.sh Payload (Exploitation) · unlock with TL Creds
- wfx-loot Azure Federated Identity Credential wfx-ci-* Added for Persistence (Post-Exploitation) · unlock with TL Creds
- wfx-loot Telegram Bot sendDocument Exfiltration from CI Hosts (C2 / Exfiltration) · unlock with TL Creds
- wfx-loot Hidden Payload Script and Loot Staging Artifacts on Disk (Exploitation) · unlock with TL Creds
- wfx-loot CI Runner Credential Harvesting - /proc Environ, Metadata Credentials and Runner Key (Post-Exploitation) · unlock with TL Creds
- wfx-loot GitHub OIDC Workload Identity Impersonating GCP Service Accounts (Cloud Lateral Movement) · unlock with TL Creds
- wfx-loot GitHub OIDC AWS Abuse - wfx-* Sessions, CreateAccessKey and Trust Policy Rewrite (Post-Exploitation) · unlock with TL Creds
- wfx-loot HTTP C2 Exfiltration Server Connections - v1 to v3 Callback IPs (C2 / Exfiltration) · unlock with TL Creds
Indicators of compromise
As of 2026-11-13, Threadlinqs Intelligence tracks 17 indicators of compromise for TL-2026-5982.
Sample indicators (3 of 17)
- IP address:
45.61.182[.]54 - Domain:
api[.]telegram[.]org - IP address:
18.143.168[.]96
Defanged, TLP:CLEAR. Full set of 17 indicators: unlock with TL Creds.
Technical analysis
It exfiltrates repository/org secrets, GITHUB_TOKEN, AI provider keys and OIDC-minted cloud credentials over Telegram and HTTP C2. GitGuardian's dataset holds 28,500 payload patches from 2,367 accounts across 17,715 repositories.
wfx-loot is a CI secret stealer shipped in four wrappers: (1) a throwaway workflow .github/workflows/build-audit-xxxxxx.yml on a chore/xxxxxxxx branch requesting id-token: write and status: write (the most common form); (2) an added 'Restore cache xxxx' step in an existing default-branch workflow, after a 'Pass runtime' step; (3) an npm lifecycle hook, consisting of a hidden .ci-xxxxxx.sh script at the repository root plus an edited package.json (usually postinstall, or prepended to an existing script such as build); and (4) the payload appended to an existing shell script (start.sh, deploy.sh) that a workflow already calls. A probe pull request titled 'docs: dependency audit notes' from branch wfx-docs adding docs/wfx-audit.md (no payload) was opened in large numbers (66,001 found by GitHub search on Nov 12). 28,148 'ci: refresh build cache' commits landed on default branches between Nov 10 and Nov 12.
The campaign ran in two phases. A small v1 precursor ran Oct 24-29, 2026 (80 payload patches, 7 accounts, 12 repositories, all four delivery forms; first npm-hook commit Oct 25 21:56 UTC). After a pause Oct 30 - Nov 9, the mass wave began Nov 10 12:45 UTC with v2a, replaced by v2b at 16:35. A second C2 IP first appeared Nov 11 05:02. Hook commits peaked at 838 per three hours on Nov 11 (09:00-12:00 UTC) and 1,210 per three hours on Nov 12 (06:00-09:00 UTC). On Nov 12, v3 added Telegram delivery (01:07) and then switched to Telegram-only, with the HTTP callback pointed at a dead address (01:48).
GitHub's approval hold stopped the workflow-file forms: all 7 visible November payload runs were held with conclusion action_required, although the actor deleted most runs (37 of 65 November injection commits show run-number gaps). The npm hook form changes no workflow file, so existing trusted workflows trigger it on npm install / npm ci / pnpm install. Of 30 sampled repositories that ran jobs on hook commits, none was blocked; public logs from 19 repositories showed execution and 18 reported successful exfiltration. The hook also runs on developer machines. The payload exits 42 only if every exfiltration channel fails, which breaks the install or build.
The stealer collects all repository and organization secrets and variables (toJSON(secrets), toJSON(vars)), the job GITHUB_TOKEN, step environment, /proc/*/environ and /proc/*/cmdline, environment variables matching about 90 AI-provider prefixes, a regex memory scan for eight token families (JWT, GitHub, GitLab glpat-, AWS AKIA, Anthropic sk-ant-, OpenAI sk-proj-/sk-svcacct-, Hugging Face hf_), 90+ credential file paths plus a home-directory search (cloud, container, Git, package registry, database, SSH, GPG, AI coding tools), and an archive of up to 60 files matching 92 secret patterns, the last 100 commits as JSON and artifacts of the last 10 runs. On self-hosted runners it also takes cloud metadata credentials, docker inspect output, the runner .credentials and RSA key, _diag logs, systemd units and persistent work-folder secrets. When id-token: write is present it mints OIDC tokens for AWS, Azure and GCP audiences and abuses them: AWS role exchange, chained AssumeRole with session tags, IAM user listing, iam:CreateAccessKey for up to two users and attempted trust-policy rewrites; GCP ADC/token conversion, project listing and service-account impersonation via generateAccessToken and signJwt; Azure ARM tokens, subscription and Key Vault listing, Azure CLI refresh-token replay to Microsoft Graph and addition of federated identity credentials; plus Vault JWT login and Kubernetes API secret reads.
Exfiltration channels: Telegram sendDocument (5 bot tokens tried in turn; v3 only, primary from Nov 12), HTTP POST to WF_CALLBACK/WF_CALLBACK2 (v1-v3), and in-GitHub channels (Actions cache, step summary base64 between <!--WFX-BEGIN--> markers, artifact wfx-loot-<run id>-<job>-<pid>) gated on WF_PRIVATE=1 and therefore unused on public repositories; encrypted commit to ci-* branch (WF_FALLBACK=0) and base32 run-log exfil (WF_EXFIL_LOG=0) were off in samples.
Attribution: the source names no actor. About 431 accounts (roughly 20%) were already compromised in the earlier GhostAction wave, and in several repositories the GhostAction security-audit.yml workflow still ran on wfx commits, but GitGuardian notes credentials are traded and reused so overlap does not prove a single actor. StepSecurity is cited as linking some stolen tokens to the Fly.io GitHub App.
MITRE ATT&CK techniques by tactic:
Credential Access
T1003.007 Proc Filesystem; T1528 Steal Application Access Token; T1552.001 Credentials In Files; T1552.004 Private Keys; T1552.005 Cloud Instance Metadata API
Execution
Command and Control
Initial Access
T1078 Valid Accounts; T1195.002 Compromise Software Supply Chain
Persistence
T1098.001 Additional Cloud Credentials; T1546 Event Triggered Execution
Collection
T1119 Automated Collection; T1213.003 Code Repositories
Defense Evasion
Exfiltration
Remediation
Immediate actions
- Determine execution: for the hook form look for '> sh ./.ci-xxxxxx.sh' in CI run logs and for installs run without --ignore-scripts, and check developer machines that ran npm install on affected repos since Oct 24; for workflow forms check the org audit log for approval/execution and assume it ran if in doubt
- If executed, rotate all repository and organization secrets and variables reachable by the job, runner environment/memory secrets, runner credential files and secrets present in the repo's last 100 commits
- Revoke the compromised account's PATs, OAuth and GitHub App grants (StepSecurity linked some tokens to the Fly.io GitHub App), SSH keys and any bot account with branch-protection bypass
- Block egress to the listed C2 IPs and monitor for api.telegram.org sendDocument traffic from CI runners
Workarounds
- Delete the malicious .ci-xxxxxx.sh, package.json hook, build-audit workflow, injected 'Restore cache' step or appended script lines and revert 'ci: refresh build cache' commits
- Set workflow permissions to read-only by default
Longer-term hardening
- Install npm dependencies in CI with --ignore-scripts where possible
- Restrict id-token: write to jobs that need it and constrain cloud OIDC trust policies by repository, branch and environment
- Require approval for workflow changes and enforce branch protection without bot-account bypass; enforce 2FA and short-lived fine-grained tokens
- Hunt for artifacts of the attack: AWS IAM keys created since Nov 10, trust-policy changes and sessions named wfx-*; Azure federated credentials wfx-ci-* and unexpected refresh-token clients; GCP generateAccessToken/signJwt calls; Vault/Kubernetes JWT logins and secret reads by GitHub OIDC identities; GitHub commit statuses with description 'wfx' and branches ci-<run>-<job>-<pid>, chore/xxxxxxxx, wfx-docs
Affected and fixed versions
- GitHub — GitHub Actions workflows and public repositories whose account credentials were stolen
Vulnerable versions: Repositories pushed to by compromised accounts Oct 24 - Nov 12, 2026 - npm — npm/pnpm lifecycle scripts (postinstall, build) in affected repositories
Vulnerable versions: Installs run without --ignore-scripts on affected commits
Timeline
- First wfx-loot payload commit (v1, throwaway workflow) at 00:05 UTC; v1 precursor runs Oct 24-29 with 80 payload patches, 7 accounts and 12 repositories across all four delivery forms.
- First npm lifecycle-hook commit (21:56 UTC), the form later used to bypass GitHub's workflow-file approval hold.
- Payload commits stop; no payload commits are seen from Oct 30 through Nov 9.
- Mass wave begins with v2a at 12:45 UTC; v2b replaces it at 16:35 UTC.
- First commit using a second C2 IP at 05:02 UTC; hook commits jump to 838 per three hours (09:00-12:00 UTC) and 595 in the following slot; no campaign commits on default branches 15:00-24:00.
- GitHub search finds 66,001 probe pull requests titled 'docs: dependency audit notes' created since Nov 10; 28,148 'ci: refresh build cache' commits on default branches Nov 10-12.
- v3 adds Telegram delivery at 01:07 UTC and switches to Telegram-only at 01:48 UTC with the HTTP callback set to a dead address; second hook burst of 1,210 commits per three hours at 06:00-09:00 UTC.
- GitGuardian publishes its wfx-loot analysis: 28,500 payload patches from 2,367 accounts across 17,715 repositories; campaign still active as of Nov 12.
Sources cited
- GhostAction Returns (StepSecurity) stepsecurity.io · Research
- GhostAction campaign: over 3,000 secrets stolen through malicious GitHub workflows (StepSecurity) stepsecurity.io · Research
- Wiz Threat Center: GhostAction campaign Wiz · Research
- wfx-loot: a CI secret stealer pushed with stolen GitHub credentials (GitGuardian, Gaetan Ferry) blog.gitguardian.com · Blog · 2026-11-13
- GhostAction campaign: 3,325 secrets stolen (GitGuardian) blog.gitguardian.com · Blog · 2025-09-05
- New GhostAction Wave Hits Hundreds of Repos, Expanding Beyond CI/CD Secrets to Cloud Credentials (Socket) socket.dev · Blog
- GhostAction Hacking Wave Fizzles, But Attackers Lurk on in GitHub (Dark Reading) Dark Reading · News
Further reading
Drafted by the Threadlinqs research pipeline from 7 sources; published ; last updated —.