# Accidentally pushed an API key to GitHub? — LeakWatch

> A calm, step-by-step guide for when you just leaked a secret to a public repo: rotate first, clean history second, check for abuse third — with the exact commands and provider links.

Source: https://leakwatch.net/blog/i-accidentally-pushed-an-api-key-to-github

---

[← Blog](/blog)

# I accidentally pushed an API key to GitHub : here's exactly what to do

July 21, 2026 · 0xCr0c0 · 6 min read

## First: breathe. You’re not the first, and it’s fixable.

You pushed. You looked at the diff a second too late. There it is, sitting in a public commit: an API key, a `.env` that wasn’t supposed to ship, a database URL with the password right in it.

If you’re feeling a little anxious right now, here’s the good news; that reflex is going to get this fixed fast. This happens to *everyone*, including people who’ve been shipping code for twenty years. It’s not a character flaw, it’s a random Tuesday. Let’s just deal with it, in the right order, without panicking.

One thing to get out of the way, because it changes everything you’re about to do:

> **Deleting the commit does not fix this.**

The moment a secret lands in a public commit, assume it’s already been copied. Bots scrape the public commit stream continuously (that’s literally [how tools like this one work](/blog/how-leakwatch-secret-scanning-works)), and a leaked key can be found and used within *minutes*. If you want to see how fast that really is, our [live feed of leaked API keys](/leaks) shows what the scanner is picking up right now, masked — most entries are minutes old. The objective isn’t to conceal it-that opportunity has passed. The goal is to make the leaked key **useless**.

Here’s the order that actually matters.

## Step 1 — Rotate the key. Right now. Before anything else.

This is the only step that truly closes the hole. Everything else is cleanup.

Rotating means: generate a new key, and **revoke/disable the old one** so the leaked value stops working. Do this *before* you touch git history : a scrubbed history with a still-valid key out in the wild is a false sense of safety!

Go straight to the provider and kill the exposed key:

-   **AWS** → IAM → Users → *Security credentials* → deactivate then delete the access key. (If it was a root key, rotate it and turn on MFA while you’re there.)
-   **Stripe** → Developers → API keys → *Roll key*.
-   **OpenAI / Anthropic** → API keys page → revoke, create a new one.
-   **GitHub token (PAT)** → Settings → Developer settings → Tokens → delete it.
-   **Google Cloud** → APIs & Services → Credentials → delete the key, create a new one, and add restrictions this time.
-   **A database password** → change the password / rotate the credential on the DB user immediately. If the connection string was public, treat the whole DB as exposed (yeah i know that’s terrible).

Don’t have time to figure out which provider it is or how bad it is? That’s exactly the kind of thing [a quick scan](https://leakwatch.net) will tell you: the key type, whether it’s still live, and where to rotate it. But if you already know what it is, just go revoke it. Come back after.

## Step 2 — Remove it from the git history

Now that the key is dead, clean up so you’re not leaking a *pattern* of secrets and so nobody trips over it later. Remember: removing it from the latest commit isn’t enough it’s still sitting in every commit in between!

Two solid tools for this. Pick one.

**`git filter-repo`** (the modern, recommended one):

```bash
# install: pip install git-filter-repo
git filter-repo --path path/to/leaked/file --invert-paths
```

Or, to scrub a specific string everywhere it appears:

```bash
git filter-repo --replace-text <(echo 'SUPER_SECRET_VALUE==>REMOVED')
```

**BFG Repo-Cleaner** (simple and fast for this exact job):

```bash
bfg --replace-text passwords.txt   # passwords.txt lists the strings to strip
git reflog expire --expire=now --all && git gc --prune=now --aggressive
```

Then force-push:

```bash
git push --force
```

Two honest caveats:

-   **Force-pushing rewrites history.** If other people work on this repo, warn them first, they’ll need to re-clone or reset, or you’ll create a mess.
-   **Forks and clones still have the old history.** You can’t reach those. This is another reason Step 1 (rotate) is the one that actually protects you, not this step.

While you’re in there: this key is probably not the only one your history has ever carried. Rewriting one commit doesn’t tell you what’s in the other four thousand. A [scan of your whole git history](/product#deep-scan) reads every commit on every branch, including the ones you force-pushed away, and comes back with the full list rather than the one you happened to notice.

## Step 3 — Check whether the key was already used

The key is dead and the history is clean. Last thing: make sure nobody had a party on your account in the meantime.

-   **AWS** → CloudTrail → look for API calls you don’t recognize, especially new IAM users, EC2 instances in weird regions, or billing spikes. Check the **Billing dashboard** — crypto-mining on a leaked key is the classic.
-   **Stripe / payment keys** → the events/logs timeline for requests you didn’t make.
-   **OpenAI / Anthropic / any usage-billed API** → the usage dashboard. A sudden spike = someone else spent your quota.
-   **GitHub token** → your account’s security log for actions you didn’t take.

If something looks off, rotate *everything* adjacent, not just the one key, and check whether that key could reach other systems.

## That’s it. Seriously.

Rotate → clean → check. Nail these three, and the leak goes from “uh-oh” to “all sorted”. No need to spiral about it.

If there’s one habit worth taking from this, it’s not “never make mistakes”, that’s not a real plan. It’s shortening the time between *leak* and *you finding out*. A key that’s caught and rotated in five minutes is a non-event. The same key sitting unnoticed for three days is the horror story.

The shortest version of that gap is zero: refuse the commit before it leaves. A [CI job that fails the build on a leaked key](/docs?tab=ci) is about ten lines of shell — it posts the diff it’s about to merge, reads a count, and exits non-zero. Nothing to install; the same script also runs as a `pre-push` hook, which is the only place where the fix is still free.

That gap is the whole reason [LeakWatch](https://leakwatch.net) exists: it watches your repos on the public forges and pings you the second a secret shows up, so you’re doing Step 1 while the key is still warm instead of reading about it in a billing alert. The heads-up is free — you can point it at a repo and see for yourself in about 30 seconds.

But honestly? Whether you use it or not, if you did the three steps above, you’re good. Go drink a beer 🙂

![Gabriel Diyan, founder of LeakWatch](/brand/Photo-Gabriel-Diyan.webp)

Gabriel Diyan (0xCr0c0)

Cybersecurity student, founder of LeakWatch. I built and run the scanner described here — the detection patterns, the false-positive classifier and the provider validators are mine. [More about who I am](/about).

[GitHub](https://github.com/Leakwatch-Scan) · [X](https://x.com/LeakwatchScan) · [LinkedIn](https://www.linkedin.com/in/gabriel-diyan-80a378375) · [GitHub (personal)](https://github.com/crocogab)

LeakWatch

Secrets leak into public commits every minute. This watches the forges for yours. Built and run by [Gabriel Diyan](/about), a cybersecurity student — [why LeakWatch exists](/about).

Scan

-   [Product](/product)
-   [Live feed](/leaks)
-   [Trends](/leaks/trends)
-   [API docs](/docs)
-   [CI/CD](/docs?tab=ci)

Read

-   [Blog](/blog)
-   [Changelog](/changelog)
-   [About](/about)

Verify

-   [Security](/security)
-   [Privacy](/privacy)
-   [Terms](/terms)
-   [Legal](/legal)
-   [Contact](/contact)
-   [Status](https://status.leakwatch.net)

© 2026 LeakWatch

[GitHub](https://github.com/Leakwatch-Scan)[X](https://x.com/LeakwatchScan)
