Dev.to Security ๐Ÿ” Cybersecurity ๐Ÿ‘ 0 ๐Ÿ“– 5 min read

GhostAction: a secret deleted from the repo is still in the repo

You have committed a password by mistake before. So have I. You deleted it in the next commit and moved on. Since October 7, an attack has been looking for exactly those secrets. The ones you think are gone. TL;DR: th

You have committed a password by mistake before. So have I. You deleted it in the next commit and moved on.

Since October 7, an attack has been looking for exactly those secrets. The ones you think are gone.

TL;DR: the GhostAction campaign is back on GitHub. Its fake workflow no longer steals only the repository secrets. It digs through the whole git history and collects secrets deleted years ago. An assisted scan of 19 repositories found values removed from the current files. Their validity remains unverified. The rule: a committed secret gets revoked. Deleting it does not count.

This article is for developers and teams who host their code on GitHub, public or private.

What GhostAction has been doing since October 7

GhostAction is a credential theft campaign. It targets GitHub Actions. It first appeared in September 2025. It came back this week, bigger.

On October 8, two well-known maintainer accounts were compromised. The attackers swept 345 repositories in two passes of a few minutes, according to StepSecurity. Socket has since counted more than 500 accounts and tens of thousands of repositories.

The method has three steps.

  • The attacker gains access to a developer's GitHub account. A stolen token is one possible route.
  • They push a single file: .github/workflows/security-audit.yml. The commit message says "Add security audit workflow".
  • That workflow audits nothing. It reads the secrets and sends them to the attacker's server.

What is new in 2026 sits inside the workflow. It downloads the full repository history, then runs git log -p --all. That command prints every change of every commit, on every branch. The script searches it for keys.

Deleting a secret does not delete it

Git keeps every version of every file. That is its job. When you remove a line, you add a commit saying "this line no longer exists". The old commit stays.

In a test repository, add and remove the fictional value DEMO_SECRET_NOT_A_REAL_KEY. Commit each step.

git log --all --oneline -S 'DEMO_SECRET_NOT_A_REAL_KEY'

The -S option finds the commits that added or removed that text. Only commit titles and identifiers are shown. Use a fictional value: typing a real secret would copy it into your shell history.

Until now, many malicious workflows only saw the last commit. By default, the checkout action downloads only that one. GhostAction adds one line, fetch-depth: 0, and gets everything.

What a scan of 19 repositories found

While preparing this article, an assistant ran gitleaks on 19 repositories on my machine. This open source tool searches git history for secrets.

gitleaks git . --redact

The --redact option hides the values in the output. Without it, you copy your secrets into your terminal and your logs.

The scan produced 24 findings after deduplication within each repository. The assistant's initial triage identified:

  • 18 findings apparently related to examples, tests or non-secret identifiers.
  • 6 potentially sensitive values to verify, in password or key fields.
  • Among them, 2 password values absent from the current files. They remain in an old Ansible inventory committed in January 2025.

This triage proves neither that the credentials still work nor that anyone used them. Their validity and revocation have not been checked. It shows that a removed value remains accessible in history.

Private repositories are also affected. A stolen token can read private repositories within its permissions.

Revoke first, clean up later

Faced with a secret in the history, the first reflex is to rewrite the history. That is the wrong order.

Rewriting only touches your copy and the server. Your colleagues' clones keep the old history. So do forks. And if someone already read the secret, they have it.

GitHub's documentation says the same: the first action is to revoke the secret. A changed password is worth nothing, even if it stays readable. A deleted password that still works is worth everything.

My order:

  1. I revoke or change the secret at the provider.
  2. I check the provider's logs to see whether it was used.
  3. I clean the history only if the value still gets in the way. For example if it trips the scanner on every run.

Also change the token that opened the door

StepSecurity stresses a point many teams forget. Changing the stolen secrets is not enough. You must also revoke the GitHub token that pushed the workflow.

If that token stays valid, the attacker pushes the same file again tomorrow.

That token is often a PAT created long ago, with no end date. Go and look at your list. Three simple rules:

  • An expiry date on every token.
  • Fine-grained tokens, limited to the repositories they need.
  • No workflow permission if the tool does not need it. Without it, a token cannot change .github/workflows.

That last point matters here. The malicious file is a workflow.

Check what your workflows are allowed to read

A workflow pushed to a branch can run from that branch. So protecting main is not enough.

Two settings reduce what an unknown workflow can take.

First, store sensitive secrets in a GitHub environment. An environment can be limited to some branches and require a human approval. A workflow started from an unknown branch cannot reach it.

Second, replace long-lived cloud keys with OIDC. You avoid storing a permanent cloud key in GitHub. Still revoke the old keys: migrating does not invalidate them.

A compromised workflow can obtain temporary access if the cloud authorizes it. Restrict trust conditions to the expected repository, branch or environment. Give the cloud role only the permissions it needs.

And look for the file itself. This command lists every commit that touched your workflows, on every branch:

git log --all --oneline -- .github/workflows/

Stop the next committed secret

A history scan helps find past exposures. For future commits, add a check before committing. Gitleaks has a mode built for that:

gitleaks git --pre-commit --staged --redact

You plug it into a pre-commit hook. The commit fails if the scanner detects a secret in the staged changes.

Keep the scan in the pipeline too. The local hook can be skipped. The pipeline can also be changed or bypassed if its rules allow it. Require it to pass before merging and protect its files. I cover it in my article on securing a Go supply chain.

The checklist

Treat a committed secret as exposed, even after deleting it.

  • [ ] Look for security-audit.yml in your repositories, on every branch
  • [ ] Run gitleaks git . --redact on every repository
  • [ ] Sort the alerts by hand: example, test, or real value
  • [ ] Revoke every real value, even old, even deleted
  • [ ] Revoke GitHub tokens with no end date
  • [ ] Remove the workflow permission from tokens that do not need it
  • [ ] Store sensitive secrets in an environment limited to your protected branches
  • [ ] Move to OIDC with restricted trust conditions, then revoke old cloud keys
  • [ ] Add gitleaks as a pre-commit hook and a protected pipeline required before merging

What to remember

GhostAction breaks nothing new. It reads what git has always kept. The only answer that holds is to make those old secrets useless.

Start with one repository, then verify every flagged value. The scan finds candidates. Revocation removes access.

Want an audit of your repositories and pipelines? Let's talk.

Sources: StepSecurity, GhostAction Returns ยท Socket, new GhostAction wave ยท The Hacker News, 9 October 2026 ยท GitHub, removing sensitive data from a repository ยท gitleaks

๐Ÿ“ฐ Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.