API key exposed on GitHub: how to revoke and prevent it

If an API key reached GitHub, treat it as compromised: revoke it and issue a new one right away. Deleting the commit afterwards helps little, because the key may already be copied.

Updated 5 min read

Learning objectives

After reading this article you will be able to:

  • Revoke the exposed key and check whether anyone used it
  • Remove the secret from the repository and the Git history
  • Prevent new leaks with .gitignore, environment variables and push protection
On this page

You opened the GitHub email or your provider's dashboard and saw the warning: one of your API keys is sitting in a public repository. Or you noticed it yourself right after git push. The first instinct is to delete the file and push again. Do not stop there.

Bots scan public repositories for keys, so every minute counts. Order matters too: first you strip the key of its power, then you clean up the rest.

leakyour responseCommitwith the keyin the code1Scana bot copiesthe value2Revokeand issuea new one3New keyin an envvariable4Deleting the committhe copy is already madeClean the historyafterwards, as hygiene
Revoking is what closes the door: the copy is already made, so cleaning the history comes later, as hygiene.

What should you do first when a key leaks?#

Treat the key as compromised from the moment it was published. In order:

  1. Revoke or rotate the key in the dashboard of the service that issued it (Stripe, OpenAI, AWS, Discord, whichever it is). Generate a new one.
  2. Update every place that uses the key: your application, CI and anything else that depends on it. Put the new key in an environment variable, not in code.
  3. Check usage: look in the provider's dashboard for requests, charges or resources you do not recognize.
  4. Only then clean the repository.

GitHub's documentation follows the same logic: if the leaked data is a credential, revoking or rotating it may be enough, and rewriting history is not always necessary.

Why doesn't deleting the commit help?#

Git keeps the whole history. A new commit that removes the key leaves the old commit untouched, and anyone can view its contents. Even if you rewrite history, copies exist outside your control:

  • Forks and clones made by people who copied the repository.
  • Pull requests, whose references stay on GitHub.
  • Cached views in the GitHub interface.
  • Scanners that already read the repository.

This is why revocation is the only fix that really closes the door. Cleaning history is hygiene, not security.

Does GitHub detect exposed keys on its own?#

Partly. On public repositories, secret scanning runs automatically, is free, and scans the entire history on all branches. For keys from partner providers, GitHub notifies the provider, which can revoke the credential on its own. That is why you sometimes get an email from a provider saying your key was disabled.

Detection has limits: it recognizes patterns from known providers, and a key for a small API or a database password inside a connection string can slip through.

Push protection blocks git push when it finds a known secret. For user accounts it is on by default and stops you from pushing secrets to public repositories on GitHub.com. For repositories it is off by default and an administrator has to enable it. Anyone with write access can bypass the block by giving a reason, so it is a safety net, not a guarantee.

How do you remove the key from Git history?#

Do this after revoking. If a whole file was sensitive (a committed .env, for example), GitHub documents using git-filter-repo, version 2.47 or newer:

bash
git clone https://github.com/YOUR-USER/YOUR-REPOSITORY && cd YOUR-REPOSITORY
git-filter-repo --sensitive-data-removal --invert-paths --path .env

To replace only the secret text across all files, create a file with the values to replace and run:

bash
git-filter-repo --sensitive-data-removal --replace-text ../secrets.txt

Check the result before pushing. Then force-push all references:

bash
git push --force --mirror origin

Pushing the refs/pull/* references fails because GitHub marks them read-only, and that is expected. If other branches fail, branch protection may be on: turn it off briefly and try again.

Before rewriting, know what changes:

  • Every affected commit and all later ones get new hashes, which can break tools that depend on them.
  • Commit and tag signatures are removed.
  • Open pull requests can lose comments. Merge or close them first.
  • Collaborators must clone again. Anyone who runs git pull and then git push from an old clone can bring the secret back.
  • Forks keep the data, and GitHub cannot notify their owners.

Only GitHub Support can remove cached views and pull request references, and it takes these requests when rotating credentials does not reduce the risk. If the key is already revoked, it is usually not worth the effort.

How do you stop it from happening again?#

Four habits cover almost every case.

1. Keep the key out of code. Read secrets from environment variables. On recent Node.js versions, you can load a .env file with the --env-file flag:

js
// index.js
const apiKey = process.env.API_KEY;
if (!apiKey) throw new Error("API_KEY is not set");
bash
node --env-file=.env index.js

Read more in what environment variables are and in the deploy security checklist.

2. Add .gitignore before the first commit. Include the secrets file:

gitignore
.env
.env.*
!.env.example

.gitignore does not affect files Git already tracks. If .env was already committed, stop tracking it:

bash
git rm --cached .env
echo ".env" >> .gitignore

This removes the file from future commits, not from old history. You still have to rotate the key.

3. Ship a .env.example with no real values. It documents which variables the project needs without exposing any:

bash
API_KEY=
DATABASE_URL=

See how the file works in what a .env file is.

4. Push protection and a review before committing. Keep GitHub's protection on and run git diff --staged before committing. Tools such as gitleaks, run as a pre-commit hook or in CI, also look for secrets in code.

If the leak was a bot token, the path is similar, with a few details of its own: see what to do when a bot token leaks.

How to do this on Square Cloud#

On Square Cloud, step 2 of the first section (update wherever the key is used) never touches the repository. Each application has its own environment variables, which you set in the dashboard, through the API, the VS Code extension or the CLI, with the app env set command followed by app restart. The change applies on the next restart, with no rebuild, and one application cannot read another's variables, even in the same account.

To make habit 1 stick, the dashboard imports your local .env and exports variables in KEY=VALUE format, so the file reaches the application without ever landing in a commit. If you deploy from GitHub, the official GitHub Action only needs a platform API key stored as a repository secret, in the token input; in any other CI, the CLI reads the same key from the SQUARECLOUD_API_KEY variable, with no login. Either way, no credential lives in the code.

After rotating the key, watch the application's real-time logs to confirm it came back working with the new value. See how environment variables and the GitHub Action work.

Infrastructure

L3/L4/L7 DDoS mitigation and a WAF on every plan

Learn more

FAQs

Does deleting the file or the commit fix the leak?

No. The secret stays in Git history, in forks and in clones, and automated scanners crawl public repositories for keys. The only safe fix is to revoke the key and create a new one.

Do I need to rewrite history after revoking the key?

Usually not. Once the key is revoked the old value grants no access, and GitHub's documentation says revoking or rotating the credential may be enough. Rewrite history if the repository also holds other sensitive data, such as personal data.

Does GitHub warn me when I push a key?

On public repositories, secret scanning runs automatically and, for keys from partner providers, GitHub notifies the provider, which can revoke them. Push protection for user accounts is on by default and blocks known secrets from being pushed to public repositories, but it does not recognize every kind of key.

Is a private repository also a risk?

Less exposed, but still a risk: anyone with access to the repository can read the key, and a private repository can be made public by mistake. The safest habit is never to keep secrets in code.

Next articleLeaked your bot token? What to do right nowIf your bot token leaked, reset it in the Discord Developer Portal (or with /token in BotFather) first. Learn the right order and how to check the damage.

More in Security

See all 10 articles