Rotating a stolen token is not the same as removing everything an attacker created with it. That is the operational lesson in Aikido’s November 12 investigation of wfx-loot: a campaign that used compromised GitHub accounts to inject credential-stealing code, with payload capabilities extending into cloud identity and trust policies.
The entry point matters. This was not simply a third-party action changing underneath an otherwise trusted workflow. An account with repository write access could change the workflow itself. And if that workflow could acquire a cloud identity powerful enough to create credentials or alter trust, replacing the original GitHub token would leave another recovery job unfinished.
For engineering teams, the question is therefore broader than “which secrets should we rotate?” It is “what authority could this runner acquire, and what could that authority leave behind?”
What the Wfx-loot Report Establishes#
Aikido reports that, starting November 10, 4,837 compromised accounts opened probe pull requests in 61,764 repositories. These are repository-probing figures, not a count of successful payload executions or compromised cloud environments. BleepingComputer’s reporting corroborates the campaign description but draws its measurements from Aikido, rather than constituting an independent reproduction.
The probe used a wfx-docs branch and a pull request titled docs: dependency audit notes. Aikido says 10,431 probe pull requests reached repositories the compromised account did not own, through collaborator or organization access. That makes an unfamiliar documentation PR from a familiar account worth investigating—not dismissing because the account already belongs to the team.
The reported delivery variants included changes to an existing workflow, a new workflow on a throwaway branch, and an npm postinstall hook invoking a shell script. Aikido describes rapid reverts, within at most a minute, followed by deletion of workflow runs and their logs.
That combination separates three questions responders should not collapse:
- Was an account compromised? Investigate the unauthorized probe and the account’s access, even if a later workflow failed to execute.
- Did the payload execute? Correlate commits, available run records, runner telemetry, and network evidence.
- What did it reach or change? Investigate the workflow’s secrets, cloud sessions, permissions, and downstream audit records.
A clean current branch cannot answer the second or third question.
SHA Pinning Does Not Protect a Workflow From Its Editor#
The tj-actions incident illustrates why mutable third-party action references are a dangerous dependency boundary. Pinning an action to a reviewed commit remains useful, but it addresses a different problem from a compromised collaborator editing your own workflow.
An attacker who can insert a shell step does not need to change the pinned action. The relevant controls are the restrictions on workflow changes, who can bypass them, which events can execute privileged jobs, and which identities those jobs can obtain.
I would review effective permissions rather than stop at “branch protection is enabled.” Check workflow-file review ownership, administrator and automation bypasses, environment approvals, and whether a newly introduced branch can run a job with deployment authority. Treat each as a control to verify in your configuration, not a guarantee implied by the feature’s name.
The Codecov credential-exposure case provides the broader context: build-system access can become access to services outside the build system. Wfx-loot adds a particularly important recovery concern—changes to the mechanism that grants that access.
Cloud Federation Is a Trust Relationship, Not a Stored Secret#
GitHub’s OIDC documentation explains the intended model: a cloud provider trusts a specific workflow identity, validates token claims against configured conditions, and issues short-lived cloud credentials. This avoids keeping a long-lived cloud key in GitHub secrets.
That is an improvement, not a reason to abandon OIDC. But short-lived credentials limit the lifetime of a session; they do not make code executing inside an authorized job trustworthy. A malicious step running under an accepted identity may be able to use that identity’s permissions.
Aikido describes several relevant payload capabilities:
| Capability Described by Aikido | What I Would Investigate |
|---|---|
| Exchange GitHub OIDC tokens for cloud sessions | Which trust conditions matched, which role was issued, and what it could access |
| Chain into AWS roles and create IAM access keys | Role-assumption events and new credentials for reachable IAM users |
| Rewrite an AWS role trust policy | Unexpected principals, removed restrictions, and changes from the approved policy |
Add Azure application federated identity credentials named wfx-ci-… | New federation entries and the issuer, subject, and audience they trust |
| Use collected GCP credentials to impersonate service accounts | Token-generation activity and the permissions of the impersonated identities |
These are not findings that every affected repository experienced every operation. Successful changes require the relevant credentials, permissions, and reachable services. The public report describes what the script can attempt; your provider audit records must establish what succeeded in your environment.
The Azure example makes the rotation problem concrete. Microsoft documents federated identity credentials as a trust configuration that checks an external token’s issuer, subject, and audience. A newly added trust entry is not the same object as an application client secret. Replacing that secret is therefore not a review or removal of the entry.
Nor does federation automatically authorize every workflow in a repository. The actual claims and matching conditions matter. Investigate the configured trust rather than assuming either universal access or universal safety.
Missing Logs Are an Investigation Gap, Not an All-clear#
The Aikido analysis distinguishes supported exfiltration channels from enabled ones. Although the payload supports GitHub caches, artifacts, and step summaries, the campaign configuration described in the report used direct HTTP POST exfiltration to attacker infrastructure. The GitHub-hosted channels were not enabled in that configuration.
Consequently, not finding a wfx-loot artifact does not establish that nothing left the runner. Likewise, a reverted commit does not undo a request already made to a cloud API.
Aikido identifies gaps in workflow run history as a useful trace after deletion. I would use such a gap as a lead, not standalone proof of this campaign: correlate it with suspicious commits, timestamps, actors, and other retained evidence. Conversely, absent logs can prevent you from proving that no cloud changes occurred. Record that limitation explicitly.
Recover the Access Graph Before Resuming CI#
My response sequence would follow the authority available to the affected job, rather than a list of secret names alone.
First, contain the source of access. Revoke compromised GitHub credentials, review the account’s sessions and grants, and stop affected workflows from acquiring fresh privileged access. Preserve available evidence without delaying urgent containment. Adding replacement secrets to a repository still writable by the attacker risks exposing the replacements too.
Second, map the reachable identities. Record repository, workflow, runner, cloud role or application, permissions, credential identifiers, and owner. Include identities obtainable through role chaining or impersonation. Do not paste secret values into an incident ticket to build this inventory.
Third, examine durable changes. Compare cloud trust policies, federation entries, access keys, and identity permissions against an approved baseline. Investigate unfamiliar entries even when their names do not contain wfx. Coordinate removal or restriction with the service owner, retaining non-secret identifiers and audit evidence.
Fourth, handle sessions and runner exposure separately. Use each provider’s incident-response procedures for issued sessions; do not assume a GitHub token revocation invalidates every downstream credential. For self-hosted runners, assess host-level exposure too: Aikido describes collection from credential files, process memory, and instance metadata. I would isolate suspect runners and rebuild them from a trusted baseline before returning privileged work to them.
Finally, verify the denied path as well as the working path. A successful legitimate deployment proves only that the new configuration works. Recovery should also establish, through safe authorized checks and provider-side configuration review, that revoked credentials and unauthorized trust paths no longer grant access. This extends the distinction in the Trigger.dev recovery analysis between updating configuration and revoking authority.
My Take#
The most consequential part of wfx-loot is not the length of the stealer or the number of token formats it recognizes. It is the possibility that a deployment identity can modify the identity system that authorizes future deployments. I would treat that as a separate architectural risk from ordinary secret exposure.
My priority would be to keep routine build and deployment roles away from identity-administration privileges wherever possible. If a pipeline genuinely needs to create credentials or change federation, that operation deserves a distinct, tightly controlled path—not incidental authority inherited by every shell step in the job.
I would also change the recovery completion criterion. “All secrets rotated” is an action log, not a security outcome. The stronger criterion is that unauthorized source access is contained, reachable identities have been investigated, unapproved trust changes are removed, and the remaining uncertainty is documented. OIDC and SHA pinning still belong in the design; neither substitutes for understanding who can edit the workflow and what that workflow is allowed to become.




