WFX GitHub Actions Stealer Uses Stolen Tokens to Harvest Secrets

· Original article ↗

Summary

StepSecurity analyzed a campaign using stolen GitHub tokens to inject a stealer into repositories, collect secrets and cloud credentials, and erase workflow evidence. Newer samples send data through Telegram; more than 63,000 probe pull requests were found.

Key points

  • StepSecurity analyzed more than 100 commits and reported over 63,000 probe pull requests; the stealer itself ran only where token permissions and repository setup allowed.
  • The attacker injected the script into workflows, existing shell scripts, or npm install hooks, then often reverted the change and deleted the workflow run.
  • The script collected repository secrets, runner memory, environment variables, credential files, and cloud access tokens, and could attempt persistent AWS and Azure access.
  • In samples observed from about 05:30 UTC on November 12, data was sent only through Telegram rather than the earlier collection IP addresses.
  • GitHub records showed 173 probe pull requests from 19 accounts were made through the Fly.io GitHub App. StepSecurity said it had not established how the tokens were obtained and had no evidence of a Fly.io infrastructure breach.
  • StepSecurity recommends searching for campaign-specific pull requests and commit messages, checking for missing Actions run numbers, rotating reachable credentials, and removing cloud persistence.

Article Details

Attack Vectors
  • The attacker used stolen GitHub tokens, including personal access tokens and tokens issued to users through the Fly.io GitHub App, to open probe pull requests and push commits with repository write access. The article says it has not established how the tokens were obtained and has no evidence that Fly.io infrastructure was breached.
  • The wfx shell script was inserted into existing GitHub Actions workflows, existing shell scripts, newly added workflows, or hidden .ci-*.sh files invoked by npm scripts such as postinstall, build, or test.
  • When run, the stealer collected repository secrets and variables, the job's GitHub token, runner process memory, environment variables, credential files, repository data, and—on self-hosted runners—cloud instance metadata credentials.
  • The script used cloud sign-in tokens to open AWS, GCP, and Azure sessions, chain into AWS roles, impersonate GCP service accounts, and use cached Azure CLI refresh tokens for Microsoft Graph and Key Vault access.
  • Persistence features included changing an AWS role trust policy to allow any AWS principal, attempting to create IAM access keys, and adding Azure federated identity credentials named wfx-ci-*. The script also contained a feature to mark required checks as passed, but none of the 101 examined copies had it enabled.
  • Collected data was packed into a gzipped archive and sent by HTTP POST to collection servers or, in later samples, uploaded to a Telegram chat with sendDocument. From about 05:30 UTC on November 12, sampled copies sent data only through Telegram.
  • The attacker attempted to hide activity by reverting injected commits and deleting workflow runs and their logs, usually shortly after execution.
Defensive Notes
  • Search repositories for the reported probe pull request title and wfx-docs branch, the listed suspicious commit messages, hidden .ci-*.sh files, and the named workflow and script patterns. Check for gaps in Actions run numbers after suspicious commits and inspect GitHub audit records for deleted runs.
  • Treat accessible repository, environment, and organization secrets and cloud roles as exposed if the stealer ran. Rotate those credentials, including publishing tokens and credentials available to self-hosted runners; revoke compromised account tokens, OAuth grants, and SSH keys.
  • Remove cloud persistence: review AWS role trust policies and IAM keys, delete Azure federated identity credentials named wfx-ci-*, and review GCP workload identity activity. Also review relevant Vault and Kubernetes logins.
  • Use controls that block bulk secret access such as ${{ toJSON(secrets) }}, and ensure default-branch workflow runs are covered. Review exemptions for bot accounts, which the article says were among the compromised accounts.
  • Monitor runner process-memory reads and outbound connections. An egress allowlist can block the Telegram-only copies; the article notes that blocking the published collection IPs alone does not stop those copies.
  • Check package and dependency records for the affected versions identified in the article, including digitalelvis/neuronai-studio 3.14.3, onuragtas/docker-env v1.0.278 and v1.0.280, and vkuznet/gocurl v0.0.0-20261111095532-8d5aad0cb0da. Rebuild affected releases from clean commits where applicable.

Indicators of compromise

TypeIndicatorContext
IPV4153[.]75[.]82[.]159Third collection server used by wfx in samples that also sent stolen data to Telegram.
IPV418[.]143[.]168[.]96Collection server used by wfx to receive stolen data via HTTP POST to port 8080 at /exfil.
IPV4194[.]150[.]166[.]244Collection server used by wfx to receive stolen data via HTTP POST to port 8080 at /exfil.
SHA1ee6e5addfe591923fc385553cb66cf0d25634a6bGit blob hash of the byte-identical docs/wfx-audit.md probe file added in the attacker's pull requests.

MITRE ATT&CK

T1003.007 · Proc FilesystemThe stealer read the GitHub Actions runner process memory to retrieve secrets held there in decrypted form.T1036 · MasqueradingThe attacker used benign-looking commit messages and workflow-step names such as ci: refresh build cache and Restore cache to disguise payload additions.T1041 · Exfiltration Over C2 ChannelThe stealer uploaded its archive of collected data to configured collection servers.T1059.004 · Unix ShellThe wfx payload is a shell script executed by workflows or shell scripts already run by CI.T1059.006 · PythonThe article says the stealer uses python3 and sudo to read the Runner.Worker process memory.T1070.004 · File DeletionAfter injecting the payload, the attacker reverted commits to remove it from the current repository state.T1078 · Valid AccountsThe attacker used stolen tokens for real developer and bot accounts to open pull requests and push commits.T1098.001 · Additional Cloud CredentialsThe persistence features attempted to create AWS IAM access keys and added Azure federated identity credentials to applications.T1195.002 · Compromise Software Supply ChainThe attacker inserted the stealer into repositories, and the article reports published package versions that carried the injected code.T1528 · Steal Application Access TokenThe stealer collected the job's GITHUB_TOKEN and used cloud sign-in tokens and cached Azure CLI refresh tokens.T1550.001 · Application Access TokenThe attacker used stolen GitHub tokens, including tokens issued through the Fly.io GitHub App, to act as account owners.T1552.001 · Credentials In FilesThe stealer collected credential files for services including AWS, Azure, GCP, Docker, Kubernetes, npm, PyPI, SSH, and Vault.T1552.005 · Cloud Instance Metadata APIOn self-hosted runners, the stealer collected cloud instance metadata credentials.T1567 · Exfiltration Over Web ServiceLater copies uploaded the stolen archive to a Telegram chat using the Telegram API's sendDocument function.

Malware

Vendors

Products

Tools

Related Articles