Leaked key on GitHub
What to do when an API key ends up on GitHub: how push protection works, how to resolve a blocked push and the right order if the key has already been pushed.
- GitHub
- checked against the official docs
- examples with a fake key
Published on
What happened
An API key is a password that a service (AI, payments, email or cloud) gives your program so it can use your account. Whoever has the key acts as you on that service.
A key "leaks" when it ends up somewhere other people can see it. The most common case is writing the key in the code and pushing the project to a public GitHub repository without noticing.
The GitHub documentation says any leaked secret should be considered compromised immediately, and that automated scanners can find publicly exposed secrets in minutes.
That is why this guide has two parts: what GitHub does to block the push before the key leaves your computer, and what to do, in the right order, if it already left.
Four words that show up throughout the guide (general Git explanation, not from the GitHub documentation):
- Repository: the project folder stored on GitHub.
- Commit: a saved record of the project, a snapshot of the files at that moment.
- Push: the
git pushcommand, which sends your commits to the repository on GitHub. - History: the list of all commits. Each commit keeps a version of the files.
Every key in this guide is fake and shows up as sk-EXEMPLO-.... What comes from the GitHub documentation appears without a label. What is a recommendation from this guide is labeled "Recommendation from this guide".
GitHub push protection
Push protection checks what you are sending. If it finds a secret, it blocks the push and shows a message explaining the reason.
For users, it is enabled by default on public repositories. The documentation describes it as specific to your GitHub account and available on GitHub.com.
What it does
- Blocks the push when it finds a secret with a recognized pattern, and shows a message with the reason.
- Is enabled by default for users, on public repositories.
- Applies to your GitHub account.
What it does not promise
- Catching any key or password: the documentation describes blocking for credentials from supported providers.
- Working on private repositories: the documentation consulted says nothing about it.
- Removing a key that was already pushed: the page describes the block at push time.
The same page describes other kinds of detection (generic patterns, custom patterns and AI detection), but it does not say that push protection for users covers all of them. Generic patterns, for example, have to be enabled in the repository’s security settings.
Where to check it on your account
- In the upper-right corner of any GitHub page, click your profile picture and then Settings.
- In the Security section of the sidebar, click Code security.
- Under User, next to "Push protection for yourself", check that the protection is enabled.
Tip
Recommendation from this guide: if GitHub did not block the push, do not conclude that the key is safe. The protection recognizes patterns from supported providers and may not recognize your key’s pattern. Keep the key out of the code from the start.
If the push was blocked
When you run git push and GitHub finds a secret, the push does not go through and the terminal shows a message with the reason. It lists all the commits that contain the secret. On the official page, each item appears under locations, with the commit and the path (file and line) where the secret is.
The documentation’s rule: to resolve a blocked push, you must remove the secret from all of the commits it appears in. Removing the key only from the current file does not work if it is still in an older commit of the same sequence.
Note
Recommendation from this guide: if you also pasted the key somewhere else (a message, a screenshot or another repository), treat it as leaked and go straight to the section "If the key was already pushed: the right order".
Part A: take the key out of the code
Recommendation from this guide: keep the key in a .env file that stays only on your computer, and read the value in the code through an environment variable. The GitHub documentation recommends using environment variables instead of writing the secret in the code.
API_KEY=sk-EXEMPLO-...const apiKey = 'sk-EXEMPLO-...';const apiKey = process.env.API_KEY;import os
api_key = os.environ["API_KEY"]Many tools read .env on their own. In the others, a library that loads .env files solves it. Check the documentation of your tool.
Now stop Git from keeping the .env. The GitHub page on ignoring files says you can create a .gitignore file in your repository’s root directory. Create the file with this line:
.envIf the .env had already entered Git, the .gitignore alone is not enough. The same page says that, to ignore a file that is already checked in, you must untrack it first:
git rm --cached .envPart B: take the key out of the commits
If the key entered the latest commit on your branch, update that commit instead of creating a new one. The command below updates the original commit:
git commit --amend --all
git pushNote
General Git explanation: git commit --amend opens a text editor with the commit message. Save and close the editor to continue. If you do not want to change the message, leave the text as it is.
Tip
Recommendation from this guide: --all only includes files Git already tracks. If you just created the .gitignore, run git add .gitignore before git commit --amend --all.
If the key entered an earlier commit, find the earliest commit that contains it. The error message lists the commits and git log shows the branch history, with timestamps:
git logWith the identifier (ID) of the earliest commit that contains the key, start an interactive rebase. General Git explanation: the ~1 makes the rebase start one commit before it.
git rebase -i COMMIT-ID~1In the editor that opens, change pick to edit on the first line, the one for the commit you identified. Save and close the editor. Then remove the key from the code and run:
git add .
git commit --amend
git rebase --continue
git pushNote
There is a space between add and .. The period is part of the command.
Caution
There is no safe shortcut for a blocked push. The way forward is to take the key out of every commit it appears in.
If the key was already pushed: the right order
If the key reached the repository, deleting the file is not enough. The GitHub documentation says that removing the secret from the codebase, pushing a new commit, or deleting and recreating the repository does not prevent the secret from being exploited.
General Git explanation: the history keeps every version of the project. If an old commit contains the key, anyone who opens that commit can see it. The documentation says the secret may still exist in the repository’s commit history.
The order below follows GitHub’s official tutorial on remediating a leaked secret:
- Revoke or replace the key at the service that issued it.
- Check for misuse in the provider’s dashboard.
- Clean the history with
git-filter-repo, carefully.
Note
The official tutorial has other steps, such as identifying the secret, assessing the risk and closing the alert. This guide covers three of them: revoking, checking and cleaning.
Step 1: revoke or replace the key at the service that issued it
To revoke is to invalidate the key so it stops working. To rotate is to generate a new key in its place. According to the documentation, revoking the secret with its provider is the most important remediation step, and simply removing the secret from the codebase is not sufficient.
The page on removing sensitive data says that once the secret is revoked or rotated, it can no longer be used for access.
Do this in the dashboard of the service that issued the key. Menu names change from service to service. Look for the area with API keys, tokens or credentials and follow the provider’s own revocation instructions. If you cannot access the dashboard, ask whoever owns the key or administers the repository.
Also tell the owner of the key and whoever administers the repository, as the documentation recommends.
If the key is in use in production
Revoking the key right away can take the application down. When downtime is a concern, the documentation suggests this order: generate the new key first, with the same permissions, make the application use the new one and only then revoke the old one.
- Generate the new key at the provider, with the same permissions as the old one.
- Update the application and the other services that used the old one, and confirm they use the new one.
- Revoke the old key.
Caution
Do not leave the old key active longer than necessary. The same page says to prioritize revoking the secret immediately.
What if GitHub already revoked it?
The documentation says GitHub automatically revokes GitHub personal access tokens (PATs) leaked in public repositories. For partner-provider secrets leaked publicly, GitHub reports them to the provider, which can act.
Tip
Recommendation from this guide: do not wait for that. Revoke it yourself and confirm in the dashboard that the old key stopped working.
Step 2: check for misuse
An exposed key may have been used before you revoked it. The documentation says to check for any unauthorized access that may have occurred while the secret was exposed. In the tutorial, this step comes after revoking and updating the key.
- In the dashboard or audit logs of the key’s provider. The documentation mentions AWS CloudTrail as an example of a log that shows whether a leaked credential was used.
- In GitHub’s logs: the security log of your personal account or the organization’s audit log, looking for events tied to the secret. Your role determines what you can see.
Tip
Recommendation from this guide: look for use at times when you were not using the service, usage or charges above normal, and resources or keys you did not create. If you find something like that, contact the provider’s support.
Step 3: clean the history, carefully
Even with the key revoked and replaced, it may still exist in the commit history. The documentation says that, ideally, you should search for and remove all instances of the secret from your repository.
Note
Cleaning is the ideal, but revoking may be enough. The official removal page says that once the secret is revoked or rotated, that may be sufficient to solve your problem.
Caution
Cleaning the history rewrites the history and affects everyone who has a copy of the repository. The documentation describes it as a destructive and disruptive process that involves force pushing to the repository. The removal page says the history can be cleaned if you carefully coordinate with everyone who has cloned the repository.
The tool the documentation uses is git-filter-repo. You need a version with the --sensitive-data-removal flag, meaning at least version 2.47. The page shows installation with Homebrew:
brew install git-filter-repoOn another system, look up the installation instructions for git-filter-repo and check that the installed version is 2.47 or higher.
Clone the repository into a new folder and enter it. Replace with your own details:
git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY
cd YOUR-REPOSITORYTo erase from the whole history a file that contained the key, such as .env, use its path in Git:
git-filter-repo --sensitive-data-removal --invert-paths --path .envIf the file was ever moved or renamed, the page says to repeat --path for each old path or run the command again. To replace text inside files instead of deleting the file, it shows the --replace-text option with a list file. The page does not detail the format of that file, so check the git-filter-repo manual before using it.
Check the result before pushing. The page says to verify that everything you wanted removed is gone, but it does not explain how. Recommendation from this guide: search the whole history for the beginning of the key. The command should show no commits. General Git explanation: -S looks for commits where the given text was added or removed, and --all looks at all branches and tags. Replace sk-EXEMPLO- with the beginning of your key:
git log --all --oneline -S"sk-EXEMPLO-"The next step cannot be undone. When you are sure, push the clean version to GitHub, overwriting the repository:
git push --force --mirror originPushes to refs/pull/ will fail, and that is expected, because GitHub marks those references as read-only. If other references fail, the page says you likely have branch protection turned on for that branch.
Caution
--force discards any changes others may have made to those references while you were cleaning up the repository.
After cleaning, tell everyone who has a copy
- Anyone who cloned before the rewrite still has the old data. The page says to discard the clone and clone again.
- Anyone who runs
git pulland thengit pushwith an old copy can bring the key back. - Anyone who created branches from the old history must rebase, not merge. One merge can reintroduce the history you just cleaned.
What cleaning does not reach
According to the documentation, if you only rewrite your history and force push it, the commits with sensitive data may still be accessible elsewhere:
- In any clones or forks of your repository.
- Directly via their SHA-1 hashes in cached views on GitHub.
- Through any pull requests that reference them.
You cannot remove the data from other people’s clones. If the commit exists in any fork, it continues to be accessible there, and GitHub cannot provide contact information for the owners of those forks. Cached views and references in pull requests can be permanently removed, and that goes through GitHub Support.
GitHub Support only assists with sensitive data, and only when it determines that the risk cannot be mitigated by rotating the affected credentials. This guide’s reading, not from the documentation: the revocation in step 1 is what protects you. Cleaning reduces exposure, but it does not erase every copy.
How to keep it from happening again
The GitHub documentation says the focus with secrets should be preventing leaks. These practices help.
Key out of the code and `.env` out of Git
Keep the key in a .env file, ignored by Git with the .gitignore, and read the value through an environment variable, as in part A of the section "If the push was blocked". The documentation recommends using environment variables instead of writing secrets in the code.
Recommendation from this guide: also create a .env.example, with the variable names and fake values, so anyone using the project knows what to fill in. This file can go into Git.
API_KEY=paste-your-key-hereThe documentation also recommends rotating secrets regularly, to limit the impact of any leak.
Turn on GitHub’s protections
- On your account: check that "Push protection for yourself" is enabled, as in the section "GitHub push protection". The documentation recommends enabling it to guard against pushing supported secrets to any public repository.
- On the repository: the documentation recommends enabling push protection on the repository, if it is not already. Repository owners, organization owners, security managers and users with the admin role can do this.
- Secret scanning: on public repositories, it runs automatically for free. On organization-owned private repositories, it is available with GitHub Secret Protection enabled on GitHub Team or GitHub Enterprise Cloud.
To enable push protection on the repository:
- Open the repository’s main page and click Settings.
- In the sidebar, under "Security and quality", click "Advanced Security".
- If Secret Protection is not already enabled, click "Enable" to its right.
- In the "Secret Protection" section, click "Enable" to the right of "Push protection".
Note
Repository push protection requires GitHub Secret Protection. If the options do not show up for you, the feature may not be available on your plan or you may not have the required role. Recommendation from this guide: confirm in the GitHub documentation before relying on it.
Before you rely on this guide
- The text of the GitHub Docs pages was checked on November 9, 2026, in the GitHub.com version. The documentation changes, so check again before acting.
- The documentation consulted describes push protection for users on public repositories. It says nothing about that protection on private repositories, and this guide promises nothing about it.
- The cleanup commands rewrite the history and cannot be undone. Do it on a fresh copy of the repository and agree on it first with whoever works on it.
- The keys in this guide are fake. Never paste a real key into a guide, chat, screenshot or issue.
- The passages labeled "Recommendation from this guide" and the general Git explanations do not come from the GitHub documentation.
- This guide is educational and independent content, not affiliated with GitHub.
Source
Official GitHub documentation (GitHub.com version). Checked on November 9, 2026. The behavior may change in future versions.
Push protection: docs.github.com/en/code-security/concepts/secret-security/push-protection.
Managing push protection for users: docs.github.com/en/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/push-protection-for-users.
Working with push protection from the command line: docs.github.com/en/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line.
Remediating a leaked secret in your repository: docs.github.com/en/code-security/tutorials/remediate-leaked-secrets/remediating-a-leaked-secret.
Removing sensitive data from a repository: docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository.
Enabling push protection for your repository: docs.github.com/en/code-security/secret-scanning/enabling-secret-scanning-features/enabling-push-protection-for-your-repository.
Secret scanning: docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning.
Ignoring files: docs.github.com/en/get-started/git-basics/ignoring-files.