Megalodon - Infostealer-Harvested GitHub Credentials → 5,718 Automated Commits in Six Hours → Injected Actions Workflows → CI Secret and OIDC Theft Across 5,561 Repos
On May 18, 2026, between 11:36 and 17:48 UTC, an actor pushed 5,718 commits into 5,561 distinct GitHub repositories. Every commit added or replaced a GitHub Actions workflow carrying a base64-encoded bash payload that swept the runner for secrets: AWS credentials and IMDSv2 instance role tokens, Google Cloud access tokens, Azure instance metadata credentials, SSH private keys, Docker and Kubernetes configuration, Vault tokens, GitHub Actions OIDC tokens, GitLab and Bitbucket tokens, and more than thirty regex-matched secret patterns. SafeDep identified the campaign, which researchers named Megalodon. The commits were authored under forged identities like build-bot and auto-ci with commit messages mimicking routine CI maintenance. Hudson Rock's follow-up supplied the origin story: 331 of 978 unique usernames tied to affected repositories, over a third, matched machines already infected by infostealers. There was no clever exploit anywhere in this chain. There was a market for stolen developer credentials, and a script that used them at scale.
Hudson Rock cross-referenced the usernames behind the affected repositories against its infostealer database and found that 331 of 978, more than a third, were direct matches to computers known to be infected. That is the whole initial access story. Commodity stealers scrape saved credentials, session cookies and tokens from developer laptops, the logs are sold in bulk on criminal markets, and somebody bought enough of them to run a campaign. The same economy sits under UNC5537 / Snowflake and under the vendor endpoint that started the Vercel / Context.ai chain. Developer machines are unusually valuable in it, because a developer's browser profile and credential helper hold keys to repositories, registries and cloud accounts all at once.
Correlation: 331 of 978 unique usernames (over 33%) matched known infected computers
Analysis by: Hudson Rock, published May 23, 2026
Not involved: Any GitHub platform vulnerability
Why developers: One profile holds repo, registry and cloud credentials together
Pushes came from disposable accounts named with random eight-character strings: rkb8el9r, bhlru9nr, lo6wt4t6 and others in the same shape. The pattern is deliberately cheap, and it is also the campaign's most machine-detectable artifact: a first-time contributor with a high-entropy username pushing directly to a default branch is a two-line rule in any repository policy engine. Nothing was exploited to get here. The credentials were valid, the write permission was real, and GitHub had no reason to refuse the push.
rkb8el9r, bhlru9nr, lo6wt4t6Authorization: Legitimate write access via stolen credentials
Detection opportunity: First-time contributor, high-entropy username, direct push to default branch
Platform exploit: None
Git lets the author field be set to anything, so the commits arrived looking like the automation that every repository already ignores. Four bot names and seven maintenance-flavored messages were rotated across 5,718 commits, and the volume distribution shows the intent clearly: two of the forged author addresses account for 2,878 and 2,841 commits respectively. Anyone skimming a repository's activity feed sees CI bots doing CI things. This is the point in the chain where signed commits stop being a nice-to-have. A required signature check does not care what the author field claims, and it would have rejected every one of these pushes.
build-bot, auto-ci, ci-bot, pipeline-botMessages: Seven variants, all imitating routine CI maintenance
Distribution: Two forged author addresses carried 2,878 and 2,841 commits
Why it works: The git author field is attacker-controlled free text
Control that ignores it: Required commit signature verification
The two variants trade immediacy against stealth. SysDiag added a new workflow triggered on push and pull_request, so it collected on the next commit anybody made. Optimize-Build replaced an existing workflow and set its trigger to workflow_dispatch only, which means it never ran on its own and produced no anomalous job history at all. It waited to be invoked later with a stolen token. That choice also sidesteps GitHub's anti-recursion behavior, which suppresses workflow runs triggered by commits that a workflow itself authored: a manual dispatch is not subject to it. A repository owner scanning recent Actions runs for something suspicious would find nothing in the dormant case, because the backdoor's whole design is to have no run history until the attacker wants one.
push and pull_request, collects immediatelyVariant 2, Optimize-Build: Replaces an existing workflow,
workflow_dispatch only, dormant backdoorEvasion: Manual dispatch sidesteps GitHub's anti-recursion suppression of workflow-authored commits
Detection consequence: The dormant variant has no run history to find until it is used
Where to look instead: Diffs under
.github/workflows/, not the Actions run list
Base64 is not sophisticated obfuscation. It is enough, because it defeats a human eye scrolling a YAML diff and most naive keyword greps, and because the reviewer in this scenario is usually nobody. Once the runner executes it, the payload inherits the job's entire context: repository secrets, cloud credentials injected for deployment, the workspace, and the network position of the runner inside whatever environment it was built to reach. A CI runner is a fully privileged machine that happens to be ephemeral, and treating an injected workflow step as anything less than remote code execution with production credentials understates it.
Execution context: The runner, holding every secret the job is entitled to
Why base64 suffices: It beats a human skimming a diff and simple keyword scans
Correct framing: Injected workflow step equals RCE with production credentials
The collection list reads like a checklist of everything a build environment can be persuaded to give up. AWS credentials and IMDSv2 instance role tokens, Google Cloud access tokens and metadata, Azure instance metadata credentials, SSH private keys, Docker and Kubernetes configuration, Vault tokens, GitLab and Bitbucket tokens, and GitHub Actions OIDC tokens, alongside more than thirty regular expressions matching API keys, PEM blocks and database connection strings. Note that IMDSv2 was harvested, not bypassed: IMDSv2 defends against server-side request forgery from a different origin, and it does nothing when the attacker is already executing on the instance. The OIDC token is the highest-value item in the list, because it is the credential that federates into cloud accounts and it is what Mini Shai-Hulud used a week earlier to publish npm packages carrying valid provenance.
Platform: SSH private keys, Docker and Kubernetes configs, Vault tokens, GitLab and Bitbucket tokens
Federation: GitHub Actions OIDC tokens
Pattern matching: 30+ regexes for API keys, PEM blocks, database connection strings
Note on IMDSv2: Harvested, not bypassed. It stops SSRF from elsewhere, not code running on the host
Exfiltration went to a single hard-coded endpoint over a port that reads as TLS in a flow log. Hosted GitHub runners have unrestricted outbound network access by default, so nothing stood between the payload and its C2. Compare this with tj-actions/changed-files, which dumped secrets into publicly readable build logs and needed no network path at all: the two campaigns bracket the problem, one exploiting egress and one exploiting log visibility, and both come back to the runner holding secrets it did not need for that long.
216.126.225[.]129:8443Path: Direct outbound from the runner; hosted runners allow unrestricted egress by default
Port choice: 8443 reads as ordinary TLS in flow logs
Contrast: tj-actions needed no egress at all, printing secrets into public build logs
This is the part that turns a large credential-theft campaign into a supply-chain one, and it is worth being precise about the mechanism. The attacker did not hijack the Tiledesk npm account. They poisoned the GitHub source, and the maintainer then published from it in the ordinary way, which means the malicious releases carried entirely legitimate publisher identity. Every downstream control that verifies who published a package returned a clean answer. In the aftermath npm invalidated granular access tokens that carried write permission while bypassing two-factor authentication, and pushed maintainers toward Trusted Publishing. CISA issued an alert on May 28, 2026 covering this and the related Nx Console compromise.
@tiledesk/tiledesk-server, poisoned versions published May 19 to 21, 2026Mechanism: Source poisoned on GitHub; the maintainer published normally from it
Consequence: Malicious releases carried legitimate publisher identity
npm response: Invalidated granular write tokens that bypassed 2FA; pushed Trusted Publishing
Advisory: CISA alert of May 28, 2026 covering Nx Console and GitHub repository compromises
🛡 How to Defend Against This Chain
git config. Turn both on for default branches everywhere, and make an exception process rather than an exception habit..github/workflows/ as protected code with its own reviewers. A workflow change is a change to what executes with your production credentials, so it deserves stricter review than application code, not looser. Use CODEOWNERS to require security review on that path, and alert on any commit touching it from an account that has not contributed before. The dormant workflow_dispatch variant proves the corollary: audit workflow file diffs, not just the Actions run history, because a backdoor that has not run yet appears nowhere in the run list.