Find leaked secrets in Git and rotate them before they are used
This article was first published on the Rokkhe blog on Hashnode.
핵심 요약
자동 요약- 1This article was first published on the Rokkhe blog on Hashnode.
- 2It was drafted with AI assistance and reviewed and edited by the Rokkhe team; all commands…
- 3A practical response guide for credentials that landed in a repository or a CI log.
원문 본문
출처 · dev.toThis article was first published on the Rokkhe blog on Hashnode. It was drafted with AI assistance and reviewed and edited by the Rokkhe team; all commands and sources were checked against GitHub, gitleaks and CISA documentation.
A practical response guide for credentials that landed in a repository or a CI log. 15 November 2026, Asia/Dhaka. By the Rokkhe research team.
If an API key, cloud access key, package token or private key is in a Git commit, treat it as already exposed. Anyone who had or later gets a clone can read it, and deleting the file in a later commit does not remove it from history. The useful work is to find every copy, rotate the credential, and stop the next one from being pushed.
This post uses official GitHub secret-scanning guidance, the open-source gitleaks scanner, and CISA’s public advice after a widely used GitHub Action leaked secrets into logs: identify what was exposed, and rotate it. It does not walk through attacking someone else’s repository.
TL;DR
- A secret in git history, in a fork, or in a CI log is a credential incident. Rotation comes first. History cleanup is optional and later.
- Scan the working tree and the full history, not only the latest revision.
- Turn on GitHub secret scanning and push protection so known secret types are blocked before they reach the remote.
- Rotate in a deliberate order, then confirm the old credential no longer works.
- A pre-commit hook and a CI scan catch the next leak earlier. They do not replace rotation of the last one.
A secret in git is already a credential incident
Teams sometimes treat this as a tidy-up: remove the line, commit again, and move on. That leaves the value in every older commit, in reflogs, in CI caches, in email notifications, and in any fork or workstation that already pulled.
GitHub’s secure-use reference says the same thing about secrets that appear in workflow logs: delete the log and rotate the secret. CISA’s March 2025 alert on the compromise of tj-actions/changed-files (CVE-2025-30066) likewise told affected organisations to identify exposed keys, tokens and private keys, and to rotate them, because values may have appeared in logs, including in obfuscated form.
So the incident question is not “is the file gone from main?” It is “what could this credential still open, and have we issued a new one?”
Scope the incident before you start rewriting git:
- Which repositories, branches and tags contain the value?
- Which CI systems and logs might have printed it?
- What can the credential do (read a bucket, push a package, deploy, clone other repos)?
- Who might already have a clone (developers, CI runners, forks, backups)?
Scan the working tree and the full history
Install gitleaks from its official repository, or use a package your team already trusts. Then run two scans: one of what is on disk now, and one of history.
# Working tree, including uncommitted files (gitleaks v8.19+ syntax). gitleaks dir . --redact --verbose # Full git history, all branches and tags. gitleaks git . --log-opts="--all" --redact --verbose --report-path gitleaks-report.json --redact keeps the secret values out of your terminal output. Older guides use gitleaks detect and gitleaks protect; gitleaks deprecated those in v8.19.0 in favour of git and dir, though they still run.
Read the report before you share it. The JSON report may still contain the secret. Store it in a restricted ticket, not in a public issue, and delete local copies when you are done.
You can also search for a known value or a known variable name, once you have one:
# Search history for a variable name, not for a live secret you then paste into Slack. git log -p --all -S 'AWS_SECRET_ACCESS_KEY' -- . ':(exclude)gitleaks-report.json' | less If the repository is on GitHub and secret scanning is enabled, also check the Security → Secret scanning tab. Partner and generic patterns may already have opened alerts. Treat each alert as a live credential until you prove it has been rotated.
Do not paste discovered secrets into chat, tickets without access control, or a paste site “for the team to see.” Refer to them by name, path, commit, and last-four characters if you must identify which one.
Turn on GitHub secret scanning and push protection
GitHub’s push protection is designed to block supported secrets before they reach the repository. That is a better place to fail than after a commit is on main.
On a repository you administer: Settings → Advanced Security (or Code security and analysis, depending on the current UI) and enable secret scanning, then enable push protection. GitHub documents the feature, including that it can cover partner patterns, generic patterns, custom patterns, and, where enabled, additional detections, with limits for very large pushes and for unsupported or legacy patterns.
You can confirm status with the GitHub CLI if you use it:
# Read-only view of repo settings, where the API exposes them. gh repo view OWNER/REPO --json name,isPrivate # For org-level or Advanced Security settings, use the UI or the # documented REST endpoints for secret scanning and push protection. Push protection is not complete coverage. A secret that does not match a known pattern will still go through. That is why a local scanner and a hook still matter. It will, however, stop a large class of cloud keys and tokens at the door, which is exactly the failure mode most teams hit.
If a developer legitimately needs to commit a test fixture that looks like a secret, use GitHub’s documented bypass and a clearly fake value (EXAMPLE / REDACTED), not a real production credential with “we will rotate it later.”
Rotate in the right order, then evict the secret from history only if you must
Rotation is the actual containment. A suggested order, from “still opens production” to “hurts less”:
- Cloud provider keys and roles (AWS, GCP, Azure, and similar). Disable or delete the exposed key first, issue a new one to a new place (a secret manager or an OIDC role, not a new line in
.envcommitted to git), then update the running systems that need it. - Package registry tokens (npm, PyPI, container registries).
- GitHub personal access tokens, deploy keys and webhook secrets.
- Application secrets (database passwords, session signing keys, encryption keys). Signing and encryption keys may need a planned cutover so you do not immediately invalidate honest sessions; still treat the old value as compromised.
- Any other token the scan found.
For each item, record: who rotated it, when, where the new value lives, and a test that the old value is rejected. GitHub’s guidance for leaked workflow secrets is the same idea in shorter form: delete the log, rotate the secret.
Only after the credential is dead should you discuss history rewriting (git filter-repo, BFG, or GitHub’s documented history-purge features). Rewriting is expensive: every clone has to be re-cloned, pull requests break, and a fork can still hold the old objects. If the repository was public even briefly, assume copies exist that you cannot rewrite. Rotation remains the control that matters.
If you do rewrite history, force-push only with a documented change window, notify every clone owner, and rotate again if there is any chance the old value was still valid during the rewrite.
Stop the next leak in local hooks and CI
Add a pre-commit hook so a secret is caught on the developer’s machine. One option is the gitleaks hook id from the pre-commit framework, which runs the same command shown below. A minimal manual hook, if you already install gitleaks on developer machines:
# .git/hooks/pre-commit (or a team hook path you already use) #!/bin/sh set -e gitleaks git --pre-commit --staged --redact --verbose Make the file executable, and prefer a hook manager your team already standardises on so this is not a one-off on a single laptop.
Then run the same scan in CI on every pull request, on a read-only workflow (see post 1 for pinning and permissions). Fail the job if gitleaks exits non-zero. Do not print the secret in the log.
Keep .env and other secret files out of git with .gitignore, and prefer short-lived credentials (OIDC to the cloud, GitHub’s documented OIDC and environment reviewers) over long-lived keys in repository secrets. Repository secrets are better than plaintext in a commit, but they are still long-lived if you leave them that way.
Checklist
- [ ] Working tree and full history have been scanned; the report is stored privately.
- [ ] Each finding is named (system, scope, last seen) without pasting the raw secret into a public channel.
- [ ] Every exposed credential has been rotated or disabled; the old value has been tested as invalid.
- [ ] Workflow logs that printed a secret have been deleted, per GitHub’s guidance.
- [ ] GitHub secret scanning and push protection are on for the repository (and the org, if you have that option).
- [ ] A pre-commit hook or equivalent local scan is documented for the team.
- [ ] CI runs a secret scan on pull requests and does not echo matches.
- [ ]
.gitignorecovers.env, key material and scan reports. - [ ] History rewrite, if any, happens only after rotation, with a plan for every clone.
- [ ] A short incident note records what leaked, what it could reach, and what was rotated.
FAQ
We deleted the file on main. Are we done?
No. The value is still in earlier commits and in any clone that already fetched them. Rotate first.
Can we just invalidate the cache or squash the latest commits?
Squashing the tip does not remove a secret from earlier objects you have already pushed. Rotation is still required.
Will push protection catch everything?
No. It catches supported patterns. Unknown or custom formats need your own scanner and a habit of not committing .env files.
Should we run a scanner against other people’s public repositories?
This post is about repositories you own or are authorized to assess. If you find a third-party secret in the wild, follow that project’s responsible-disclosure process. Do not use the credential.
Does this replace a secret manager?
No. Scanning and push protection are safety nets. New credentials should be issued into a secret manager or an OIDC role, not into a file that is tempting to commit.
Sources
- GitHub Docs, Push protection
- Gitleaks (README and
.pre-commit-hooks.yaml, checked 15 November 2026 against v8.30.1) - GitHub Docs, Secure use reference
- CISA, Supply chain compromise of tj-actions/changed-files (CVE-2025-30066) and reviewdog/action-setup
Next in the series
Build a weekly CVE triage workflow with CISA KEV, EPSS and CVSS. Previous: Test and fix the OWASP Top 10 2025 in your web app.
Rokkhe is an AI pentesting and security-operations workspace. Learn more at rokkhe.com.
이 글은 dev.to 의 원문을 정제해 보여드립니다. 저작권은 원저작자에게 있습니다.
전체 내용이 궁금하다면
dev.to 원문에서 이어 읽기




