Thousands of GitHub repos hit with secret-stealing malware
CSO Online ·

Researchers have uncovered a campaign in which hijacked GitHub accounts spread secret-stealing malware across thousands of repositories. The payload executed in at least 1,806 repos, exfiltrating CI/CD secrets and cloud credentials. Aikido Security, which detected and documented the campaign, said the attackers used the 4,837 compromised accounts to open probe pull requests in 61,764 repositories starting Nov. 10. Around 92% of those pull requests, titled “docs: dependency audit notes,” are still open, which suggests most project owners have not seen them. However, the probes on their own are benign and were performed only to check whether the hijacked accounts had write-access in those repos so that the attackers could follow up with commits that contained the malicious payload: a 1,500-line shell script that collects GitHub Actions secrets, cloud credentials, and API keys for AI services and developer tools. The payload was delivered using at least three methods: modification of existing GitHub Actions workflows, adding a postinstall hook in the package.json file that runs it on any npm install and creating a throw-away branch with a rogue Actions workflow to be run. “We’ve identified indications that the payload ran in at least 1,806 repositories, but this number may understate impact,” the Aikido researchers said in their report . “The run log was deleted in impacted repositories, meaning the threat actor has likely collected the credentials and covered its tracks.” The npm hook that slipped past GitHub GitHub’s safeguard against this kind of attack worked as designed for the payloads it could see. The platform has blunted recent workflow-injection campaigns by requiring explicit approval before the malicious workflows run, and GitGuardian found that all seven of the November payload runs still visible in workflow files sat in an action_required state and never started. “GitHub held the workflow forms, but an npm hook slipped past the hold and ran,” Gaetan Ferry, a security researcher at GitGuardian, said in an analysis . That’s because the npm hook did not change any workflow file. Instead, the attacker added a hidden script to the repository root and a single line in package.json , which runs on every npm install . The repository’s own unchanged workflows executed npm install as routine build hygiene. By Thursday the attacker had also changed their exfiltration method. The latest payload sends stolen data only to a Telegram chat, with the HTTP callback now pointing to a dead address. Blocking the previously published IP addresses no longer stops the latest versions. What the malware steals Once the script runs, it receives every repository and organization secret through a JSON dump of the workflow’s secrets object. It scans the memory of the runner process, which holds secrets in decrypted form, including ones the workflow never referenced, and collects credential files from well-known paths, shell histories, and API keys for AI services and developer tools such as OpenAI, Anthropic, Copilot, and Cursor. The script goes further and exchanges OIDC tokens for AWS, GCP, and Azure sessions, mints new long-lived AWS access keys, impersonates GCP service accounts, and replays cached Azure refresh tokens to reach Microsoft Graph and Key Vault. It packs everything it collects into a gzipped archive and sends it to the attacker then it cleans up. In most repositories the attacker reverted the malicious commit within a minute and deleted the workflow run and its logs. Backdoors that survive credential rotation The script also takes actions for attackers to maintain access even if credentials are reset. For AWS it rewrites the assumed role’s trust policy to accept any AWS account, opening the role to anyone, and calls the iam:CreateAccessKey API to create long-lived keys for IAM users. On Azure it injects federated identity credentials named wfx-ci-* into applications, pointing them back at the compromised repository, so that any workflow in that repository can authenticate to Azure without a stored secret even after every token has been rotated. If a probe pull request or any of the campaign’s commit strings appear in a repository you control, assume every secret that repository’s workflows could reach is stolen. Rotate repository and organization secrets, package registry tokens, and any cloud keys, and remember that the memory scan means the list of what was taken is wider than what the workflows referenced. Then hunt for signs of the persistent mechanisms: AWS role trust policies that accept any account, IAM keys created since Nov. 10, and Azure federated credentials named wfx-ci-* . “Ensure all logs are enabled, particularly audit logs on cloud admin behavior,” Ramy Rahman, senior principal solutions engineer at ArmorCode, tells CSO. “Inspect audit logs where possible (specifically in AWS and Azure, as persistence is possible) to ensure the malicious payload did not run.” The wfx-loot payload also shipped in published packages: the Packagist package neuronai-studio 3.14.3, and the Go modules docker-env and gocurl. In the Go modules the payload sits only in a workflow file that Go tooling never runs, so consumers may not have been affected; machines that installed the Packagist version should be treated as compromised. About a fifth of the GitHub accounts used to launch this campaign had already been compromised during the GhostAction attack back in October, GitGuardian said. Meanwhile researchers from StepSecurity traced 173 of the probe pull requests to tokens issued to the Fly.io GitHub App. Fly.io confirmed that these tokens were leaked when its deploy process left them in .git/config files inside build directories that some apps served publicly, but noted that Fly.io itself was not breached. GitHub’s bulk revocation of the Fly.io app tokens stopped partway, so some of the stolen token pool may still be valid.