Yaamlabs
Supply chain

GhostAction is back, stealing secrets via GitHub workflows

GhostAction pushed a fake security workflow into 772 public GitHub repos to send CI secrets to one IP. How it works, how to find it and what to rotate.

By Yaali. October 12, 2026, 7 min read, Supply chain, DevOps, Threat intel.

Cover illustration of a ghostly figure pulling glowing keys out of a stack of code repositories, with the Yaamlabs logo and the text: GhostAction returns to steal GitHub CI secrets, 772 public repositories hit

GitGuardian reported on October 7 that GhostAction, the GitHub Actions campaign that stole 3,325 secrets from 817 repositories in September 2025, is running again. Between August 31 and September 30, 2026, the attackers committed a workflow called "Github Actions Security" to 772 public repositories owned by 373 users and organizations. The workflow reads the repository's continuous integration (CI) secrets and posts them over plain HTTP to 193.32.204.199.

The commits came from the maintainers' own accounts. On October 8 a new variant reached about 345 more repositories through two hijacked accounts, and this one also searches the full git history for deleted keys. If anyone with write access to your repositories has had a token or session stolen, check your .github/workflows folders today.

Four steps of a GhostAction injection, from a stolen maintainer credential to a curl POST of secrets to 193.32.204.199

How it works

A GitHub Actions workflow is a YAML file in .github/workflows/ that runs on GitHub's runners when an event fires, such as a push. Secrets stored in the repository or organization settings are handed to a job only when the workflow names them, as ${{ secrets.NAME }}. GhostAction uses exactly that mechanism.

The attacker starts with a working GitHub credential for a maintainer. StepSecurity thinks the likeliest source is a personal access token from infostealer logs or a credential dump. The attacker reads the repository's existing workflows, collects the secret names they reference and writes them into a template. The result is committed straight to the default branch with the message "Add Github Actions Security workflow". No pull request is opened, and the commit carries the victim's real name and email.

The file is github_actions_security.yml, with one job, send-secrets, on ubuntu-latest. A step called "Prepare Cache Busting" writes a timestamp to $GITHUB_ENV, and the next step runs curl -s -X POST with each named secret as form data to http://193.32.204.199. Triggered on push and workflow_dispatch, it fires on the commit that adds it and then on every later push by the real maintainers, which is why many of the observed runs were started by legitimate work.

Two smaller variants exist. On September 7 seven repositories received security-check.yml ("Add security check workflow"), which posts to port 3000 of the same IP with an inj=<id> parameter so the operator can tell which repository each secret came from. In 92 cases the attacker did not add a new file at all, and instead edited a workflow left over from an earlier GhostAction wave to point it at the new server.

The October 8 variant, security-audit.yml, adds a history sweep. It checks out with fetch-depth: 0, then runs git log -p --all and greps every past diff for 13 credential patterns: AWS key IDs, secret keys and session tokens; Anthropic, OpenAI and OpenRouter API keys; GitHub classic and fine-grained tokens and GitLab tokens; Google API keys, Slack tokens and SendGrid keys. For each AWS key ID it also captures two lines either side, to pair the ID with its secret. A key committed in 2021 and removed a week later is exposed if it was never revoked.

Because the endpoint is a raw IP over HTTP, the job makes no DNS lookup, and DNS-based filtering on self-hosted runners never sees it.

What attackers are doing

GitGuardian's Gaetan Ferry and Guillaume Valadon saw the injections arrive in bursts: 143 repositories on August 31, roughly 400 between September 2 and 5, and 103 on September 15, with smaller batches through the end of the month.

Six stat tiles for the August 31 to September 30 wave, from 772 repositories hit to 26 secrets confirmed stolen and 124 repositories cleaned

The 772 malicious workflows named 2,577 secrets between them. Some are harmless values like hostnames and ports. The credentials among them include 446 SSH private keys and deployment server logins, 218 Azure credentials, 142 container registry logins for Docker Hub and GHCR, 112 database credentials, 106 AWS access keys, 92 FTP logins, 80 Google Cloud and Firebase credentials and 66 GitHub tokens.

Most of the attempts failed. Of 3,669 runs GitGuardian collected across 605 repositories, most sat waiting for approval and never executed; 499 ran, in 32 repositories. GitGuardian confirmed 26 secrets stolen from 13 repositories, and noted that new runs were still firing as it wrote. Clean-up has been slow: by October 5 only 124 of the 772 repositories, about 16 percent, had the workflow removed in their public history.

The October 8 wave moved faster. The attacker pushed security-audit.yml to 27 repositories of the kitao account, including the Pyxel game engine, whose copy named its PyPI and crates.io publishing credentials. Later the same day the henrywoo account pushed it to 318 repositories, 39 of them originals and 279 forks, and through that account's write access to uber/athenadriver. StepSecurity counts 345 repositories in total and Socket counts 346. The run in uber/athenadriver completed and sent its data, and StepSecurity had seen no malicious package release by October 9.

What to do

  1. Search your organization. In GitHub code search, run "193.32.204.199" org:your-org, "AKIA_CTX_START" org:your-org and path:.github/workflows "send-secrets" org:your-org. Code search skips forks, so check those separately.
  2. In each clone, git log --all --format='%h %an %ad %s' -- .github/workflows/github_actions_security.yml .github/workflows/security-check.yml .github/workflows/security-audit.yml lists every commit on any branch that touched the three file names. The commit messages to look for are "Add Github Actions Security workflow", "Add security check workflow" and "Add security audit workflow".
  3. gh run list --repo owner/repo --workflow github_actions_security.yml shows whether the workflow executed. Treat any completed run as confirmed theft of every secret the file names. A run stuck in action_required did not execute; delete it so nobody approves it later.
  4. Revoke the credential that made the commit. The commit author tells you whose account was used. Sign that user out everywhere and revoke their personal access tokens, OAuth app grants and SSH keys. Rotating repository secrets alone leaves the attacker able to inject again tomorrow.
  5. Remove the workflow from every branch, not only the default one, and from forks before anyone enables Actions on them.
  6. Rotate every secret named in the malicious file, including ones GitGuardian did not list as confirmed stolen. If security-audit.yml ran, also run git log -p --all yourself and revoke every credential that ever appeared in history. For AWS keys, review CloudTrail from the run time onward for new IAM users, new access keys and activity in regions you do not use.
  7. If the workflow named PyPI, npm, crates.io or registry credentials, compare recent releases against your own build output.
  8. Block 193.32.204.199 (ports 80 and 3000) at the egress firewall and search firewall and proxy logs from self-hosted runners back to August 31.

Making the next injection fail

In every hit repository, one stolen credential was enough to add code that runs with the repository's secrets.

Put deployment and publishing secrets in GitHub Environments with required reviewers. The job then waits for a named person to approve before the secret is released, and an injected workflow that does not reference the environment gets nothing. Where the target supports it, replace stored cloud keys for OpenID Connect (OIDC) federation, where the job requests a short-lived token at run time instead of reading a long-lived key.

Classic personal access tokens need the workflow scope to create or edit files in .github/workflows, so find out which of your people's tokens carry it and move them to fine-grained tokens limited to named repositories. Add a ruleset on the default branch that requires a pull request, with no bypass for admins, plus a CODEOWNERS entry for .github/workflows/ with code owner review required, so a workflow change needs a second person. On runners, an outbound allowlist turns a raw-IP POST into a failed connection. Turn on secret scanning with push protection so fewer keys reach history in the first place.

We review CI/CD pipelines, token scopes and workflow permissions in our red team and code review work and harden GitHub organizations as part of platform and cloud engineering. For a related route into workflows through hijacked action tags, see our post on Mini Shai-Hulud. For a review of your workflows or help with a rotation plan, open the chat and Yaali, our AI agent, will pass your question to an engineer.


Sources: GitGuardian, StepSecurity, Socket, The Arabian Post, Risky Bulletin.

Read next

Back to the blog, or tell us about your system in the chat. Yaali, our AI agent, answers first and brings in an engineer.