4,837 GitHub Accounts Planted a Secret Stealer in Repositories

4,837 GitHub Accounts Planted a Secret Stealer in Repositories

4,837 GitHub accounts were used to test write access to 61,764 repositories and, in some of them, install a secret stealer in continuous integration workflows. Aikido Security counted executions in at least 1,806 repositories. The campaign, detected starting Tuesday, November 10, exploited stolen credentials and in many cases deleted both the malicious changes and the execution logs.

The attack was not limited to repositories owned by the compromised accounts: the probes also reached projects accessible through collaborator or organization permissions. And its scope is measured in several ways. In an independent analysis, SafeDep found malicious commits in 8,093 public repositories; WebPulse, based on an Aikido sample, gives an estimate of approximately 6,500 repositories where the payload may have run. These counts use different methods and coverage, and are not interchangeable figures.

A pull request to check permissions

The activity began with a pull request titled docs: dependency audit notes, submitted from a branch called wfx-docs. Its purpose was to check whether the stolen account could write to the repository. Aikido identified test requests in 61,764 repositories; some later received the malicious code.

The test itself matters even when there was no second modification. Aikido says that any account that opened one of these pull requests should be considered compromised, even if the stealer never ran. In most of the repositories tested, nothing else was pushed, or the payload did not work because of the project configuration or available permissions.

Of the pull requests submitted with collaborator or organization access, 10,431 reached repositories that did not belong to the attacked account. 92% were still open when Aikido checked. This broadens the search beyond each user's own projects: an account could use its delegated permissions to probe other repositories.

The malicious activity took several forms. In 7,309 repositories in SafeDep's count, the code was added to an existing workflow. In another 771, it arrived as an npm postinstall hook, which can also run on a developer's machine when installing dependencies.

One modification added around 1,500 lines to an existing workflow and appeared with the commit message ci: refresh build cache. Other clues included the message chore: add ci helper, alongside a .ci-xxxxxx.sh script and an install hook, or a new workflow with a name resembling chore: add dependency audit workflow. Commits with messages such as ci: restore and ci: cleanup were also observed, consistent with a sequence of introducing and removing changes.

The change disappeared; the secrets, not necessarily

After introducing the stealer, the attacker usually reverted the change within minutes. Workflow logs and often the execution itself were also deleted. Aikido found the log missing in 156 of the 159 repositories in its sample cited by WebPulse. Therefore, an apparently clean Actions history does not rule out the code having run.

A more useful signal may be a missing run number around the attacker's commit. Aikido notes that not finding visible traces such as artifacts called wfx-loot, cache entries wfx-, or summaries containing WFX-BEGIN also does not guarantee that no data was lost.

The published figures do not describe exactly the same universe. Aikido confirmed execution in at least 1,806 repositories. WebPulse reports that the company examined a random sample of 1,500 probed repositories and found execution in 10.6%, with a margin of 1.6 percentage points; extrapolated, that gives around 6,500 repositories. SafeDep, meanwhile, counted 8,093 repositories that received malicious commits between 12:46 UTC on Tuesday and 04:36 UTC on the day of its analysis. This count includes repositories where the stealer did not run and is limited to default branches of public projects, so its researchers believe the actual total is higher.

The payload searched for credentials available to the runner: GitHub Actions secrets and variables, the GITHUB_TOKEN token, OIDC tokens and Actions runtime tokens, AI service keys, and secrets present in process memory. It also read credential files for AWS, Azure, GCP, Kubernetes, npm, PyPI, SSH, and other tools in their usual locations.

It also copied the repository, its latest 100 commits, and recent workflow artifacts. On self-hosted runners, it could collect cloud credentials through instance metadata, as well as information about Docker container status and systemd units. Aikido found options for operating in private repositories, although this alone does not show how many were affected.

Access could survive key rotation

The stealer did not just collect secrets to send them elsewhere. According to Aikido, it could exchange GitHub OIDC tokens for AWS, GCP, and Azure sessions; in AWS, it could also create long-lived access keys for IAM users. Some functions were designed to maintain access even after credentials were changed.

In AWS, it could modify a role's trust policy to allow access from any AWS account. In Azure, it could add federated identity credentials with the wfx-ci- prefix to Entra ID applications. This allowed workflows in the compromised repository to authenticate without a stored secret, even after the usual tokens were rotated.

SafeDep and Aikido recommend looking for unrecognized changes in these systems: wfx-ci-* federated credentials in Entra ID; AssumeRoleWithWebIdentity sessions or calls with wfx-* names in CloudTrail, followed by CreateAccessKey or UpdateAssumeRolePolicy; GenerateAccessToken and SignJwt calls from GitHub's identity pool in GCP; and JWT access from GitHub's OIDC issuer in Vault.

SafeDep advises revoking the commit author's tokens, OAuth app grants, SSH keys, and sessions; enabling two-factor authentication; and rotating all secrets and variables the repository could read. If anyone ran npm install on an affected commit, they should also treat that machine as compromised and change the keys it contains.

“we don't attribute November to the same actor at this stage”

— Aikido Security

Aikido's warning concerns the connection to GhostAction, an October campaign that also used stolen GitHub accounts to introduce workflows that stole secrets. The accounts compromised then were about 1,000 times more likely than other active accounts to be used again this week. In more than 100 repositories, the new stealer was added to October workflows, leaving the earlier data-exfiltration step active. Aikido does not currently attribute both waves to the same actor: it points to differences in infrastructure, tools, and methods, and notes that stolen credentials can end up in the hands of multiple attackers.

Affected packages and indicators to look for

The campaign reached at least one Packagist package and one Go module through automated publishing triggers. At least 50 of the compromised accounts appear to publish packages for npm, Go, Packagist, PyPI, or crates. Aikido believes the primary goal was to steal CI secrets, not to spread through packages, but identified several versions that should be reviewed.

Version 3.14.3 of digitalelvis/neuronai-studio, published on Wednesday, is malicious. Aikido advises treating any machine that installed or opened it as compromised, blocking that version, and pinning the dependency to 3.14.2 or earlier. A normal composer install does not run the stealer; doing so requires running npm install inside the package's vendor directory.

In the Go module github.com/onuragtas/docker-env, versions v1.0.278 and v1.0.280 contain the code in a GitHub Actions workflow file. The Go code and published binaries were not changed, and Go builds do not run these workflows; even so, Aikido recommends removing those versions. It also detected the stealer in the workflow of github.com/vkuznet/gocurl, pseudo-version v0.0.0-20261111095532-8d5aad0cb0da, although that workflow had never run. The recommendation is to block it.

The code also reached 28 WebdriverIO repositories through commits attributed to the wdio-bot bot account. No package releases were affected, but the bot's token should be considered stolen.

To search for activity in repositories accessible since Tuesday, November 10, clues include the wfx-docs branch, short branches named chore/[a-z0-9]{8} or ci-check-[0-9]{4}, and the pull request docs: dependency audit notes. It is also worth reviewing the mentioned commits and files, as well as runs with missing numbers. The Hacker News lists the script's SHA-256 89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf among the indicators. SafeDep identified HTTP POST transmissions to 194.150.166[.]244:8080/exfil, 18.143.168[.]96:8080/exfil, and 153.75.82[.]159:8080, as well as a Telegram chat. In a sample of 200 commits with payloads, approximately one-fifth used Telegram alone.

The Hacker News found new pull requests with the same title after Aikido's search ended on Wednesday. Three reviewed requests, opened between 05:04 and 05:38 UTC, came from wfx-docs branches; it could not be determined whether the stealer ran in them. The published checks cover visible public repositories, while write permissions also brought the probes to organization and collaborator projects.

Sources: The Hacker News, WebPulse, Business Upturn, Secure Blog, Hamer Intelligence Services