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 push command, 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

  1. In the upper-right corner of any GitHub page, click your profile picture and then Settings.
  2. In the Security section of the sidebar, click Code security.
  3. 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.

.env file, stays only on your computer
API_KEY=sk-EXEMPLO-...
JavaScript, wrong: the key written in the code
const apiKey = 'sk-EXEMPLO-...';
JavaScript, right: the key comes from the environment variable
const apiKey = process.env.API_KEY;
Python, right: the key comes from the environment variable
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:

.gitignore file
.env

If 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:

terminal
git rm --cached .env

Part 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:

terminal, secret in the latest commit
git commit --amend --all
git push

Note

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:

terminal
git log

With 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.

terminal, replace COMMIT-ID with the real ID
git rebase -i COMMIT-ID~1

In 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:

terminal
git add .
git commit --amend
git rebase --continue
git push

Note

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:

  1. Revoke or replace the key at the service that issued it.
  2. Check for misuse in the provider’s dashboard.
  3. 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.

  1. Generate the new key at the provider, with the same permissions as the old one.
  2. Update the application and the other services that used the old one, and confirm they use the new one.
  3. 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:

terminal, example from the page with Homebrew
brew install git-filter-repo

On 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:

terminal
git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY
cd YOUR-REPOSITORY

To erase from the whole history a file that contained the key, such as .env, use its path in Git:

terminal
git-filter-repo --sensitive-data-removal --invert-paths --path .env

If 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:

terminal, recommendation from this guide
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:

terminal
git push --force --mirror origin

Pushes 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 pull and then git push with 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.

.env.example file, can go into Git
API_KEY=paste-your-key-here

The 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:

  1. Open the repository’s main page and click Settings.
  2. In the sidebar, under "Security and quality", click "Advanced Security".
  3. If Secret Protection is not already enabled, click "Enable" to its right.
  4. 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.