Security

Leaked an API Key on GitHub? What to Do in the Next 30 Minutes

9 min read · Updated October 16, 2026 · Checked against official docs

Maybe GitHub emailed you about a “secret detected” in your repo. Maybe a provider sent a warning that your key was found in public. Maybe you just noticed .env sitting in your commit list. Either way, your stomach dropped.

Take a breath. This is fixable, and the order you do things in matters more than speed. Here’s the most important sentence in this guide:

Deleting the file does not un-leak the key. Revoking the key does.

GitHub’s own docs are clear that removing a secret from your code, pushing a new commit, or even deleting and recreating the repository is not enough. The key has to be revoked with the service that issued it. (GitHub: Remediating a leaked secret)

Step 1 (first 10 minutes): figure out what leaked

Open the commit and list every secret in it. A leaked .env file often contains several. For each one, ask: is this a secret key, or a public one?

Usually safe to be publicMust be rotated if leaked
Supabase publishable key (sb_publishable_...) or legacy anon keySupabase secret key (sb_secret_...) or legacy service_role key
Stripe publishable key (pk_live_..., pk_test_...)Stripe secret key (sk_...), restricted key (rk_...), webhook secret (whsec_...)
Firebase web configOpenAI, Anthropic, Resend, SendGrid and most other “API keys”
Your Supabase project URLDatabase passwords and connection strings (postgres://...)
GitHub tokens, cloud provider keys (AWS, Google Cloud), OAuth client secrets

The left column is designed to sit in browser code. Supabase’s docs describe the publishable key as safe to expose in web pages and source code, because what protects your data is Row Level Security, not that key. (Supabase: API keys) Not sure which kind you have? Environment Variables and API Keys, Explained walks through it.

Also note whether the repo is public or private. A private repo leak is still worth fixing, but a public one should be treated as already copied. Automated scanners watch public commits constantly.

Step 2: revoke and replace each secret

Do this for every secret from the right-hand column. The pattern is always: create a new key, put it everywhere the app needs it, then kill the old one. If the repo is public, you can reverse the order for anything that can spend money (kill first, fix the app after). A few minutes of downtime is cheaper than a surprise bill.

Stripe secret or restricted key

In the Dashboard, go to Developers → API keys. Stripe lets you rotate a key: you pick when the old key expires, and choosing Now deletes it immediately. If you schedule it instead, the old and new keys work side by side for up to 7 days. For a key that leaked publicly, choose Now, or expire it from the key’s overflow menu. Stripe’s docs list a compromised key as the reason to do exactly that. (Stripe: API keys)

If a webhook signing secret (whsec_...) leaked, roll it on the webhook endpoint’s page, and update your app’s environment variable. Our Stripe webhook guide covers where that secret lives.

Supabase secret key or service_role key

This is the big one: Supabase’s secret keys bypass Row Level Security, so whoever has one can read and change everything. Supabase’s documented rotation steps:

  1. Create a new secret key in Settings → API Keys.
  2. Replace the leaked key everywhere your app uses it (Edge Function secrets, your host’s environment variables, your server).
  3. Confirm every part of the app now uses the new key.
  4. Delete the leaked secret key. That can’t be undone, which is why it’s last. For a leaked legacy service_role key, deactivate the legacy keys in the same section instead. That’s reversible, so you can re-enable them if you missed a client.

Supabase also notes that the legacy anon and service_role keys are being deprecated by the end of 2026, so if a legacy key leaked, this is a good moment to move to the new publishable and secret keys. (Supabase: Rotate a leaked key)

If your database password or connection string leaked, reset the database password in your project’s database settings, then update anything that connects directly.

OpenAI, Anthropic, Resend and other API keys

Most services don’t “rotate” keys. You delete the leaked one and create a new one on the same page:

  • OpenAI: the API keys page in the OpenAI platform dashboard.
  • Anthropic: API keys in the Claude Console.
  • Resend: API Keys in the Resend dashboard.

While you’re creating replacements, give each new key the smallest permissions the app needs (many services offer “sending only” or project-scoped keys). Smaller keys mean smaller leaks.

GitHub tokens and cloud keys

Delete leaked personal access tokens under GitHub Settings → Developer settings. For AWS or Google Cloud keys, follow the provider’s compromised-credential guidance, and check whether the key could create resources. These are the leaks that most often lead to large bills.

Step 3: update your app and check it still works

Put the new keys in the right place, never in the code:

  • Lovable: the project’s secrets settings (Lovable Cloud) or Supabase’s Edge Function secrets.
  • Bolt / Cursor / your own repo: a local .env file that’s in .gitignore, plus your host’s environment variable settings (Vercel, Netlify, Cloudflare).
  • Supabase Edge Functions: Edge Function Secrets in the dashboard, or supabase secrets set NAME=value.

Redeploy, then test the features that use each key: sign in, a test payment, an email, an AI call.

Step 4: look for misuse

Rotating stops future abuse. It doesn’t tell you what already happened. For each key, check its provider’s activity:

  • Stripe: the key’s request logs, plus recent payments, refunds, payouts and new API keys or webhooks you didn’t create.
  • Supabase: the logs for your API and database, plus any tables, rows or users you don’t recognize.
  • AI providers: usage and billing pages for spikes. Set a monthly spending limit if the provider offers one.
  • Cloud providers: new resources, users or keys you didn’t create.

If you see charges you didn’t make, contact the provider’s support. Many have a process for abuse after a leaked key.

Step 5: clean up Git history (optional, and do it last)

Once every leaked secret is revoked, the copy in your Git history is a dead key. GitHub’s docs say rotating may be enough on its own, and that rewriting history may not be warranted, because it’s disruptive: it changes commit IDs and needs a force-push. (GitHub: Removing sensitive data)

If you still want it gone (for example, the file also contains customer data), GitHub recommends git-filter-repo version 2.47 or newer. To remove a file from all history:

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

Or to replace specific secret values everywhere they appear, list them in a file outside the repo (one per line) and run:

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

Then force-push, following GitHub’s guide step by step. Back up the repo first. GitHub warns that the old commits can still exist in clones, forks, cached views and pull requests, and that GitHub Support can help remove cached views for real sensitive data. This is why rotation, not cleanup, is the fix.

If the commit hasn’t been pushed yet, it’s much simpler: undo the commit locally, remove the secret, and commit again. Note that git revert doesn’t help here. It adds a new commit and leaves the secret in the old one.

Step 6: make sure it can’t happen again

1. Ignore your env files. Add this to .gitignore and commit it:

.env
.env.*
!.env.example

If .env was already tracked, .gitignore alone won’t stop Git from tracking it. Remove it from tracking (the file stays on your computer):

git rm --cached .env
git commit -m "Stop tracking .env"

2. Keep an .env.example with the variable names and fake values, so your AI tool and collaborators know what’s needed without seeing real keys.

3. Turn on secret scanning and push protection. In your repository’s Settings → Advanced Security (or Code security, depending on your GitHub plan and UI), enable secret scanning and push protection where available. Push protection blocks pushes that contain known secret formats before they reach GitHub. (GitHub: Secret leakage risks)

4. Keep secrets out of browser code. Anything with a VITE_ or NEXT_PUBLIC_ prefix ends up in your JavaScript bundle for anyone to read. A secret there is leaked the moment you deploy, Git or no Git.

5. Don’t paste secrets into AI chats. Tell the AI the variable name and let it reference process.env.STRIPE_SECRET_KEY or Deno.env.get('STRIPE_SECRET_KEY').

A prompt for your AI tool

Scan this whole project for hard-coded secrets: API keys, tokens, passwords,
connection strings, and any value that looks like sk_, rk_, whsec_,
sb_secret_, service_role, or a long eyJ... JWT. Also check .gitignore covers
.env files, and list any VITE_ or NEXT_PUBLIC_ variables that hold secrets.
Report findings only. Don't print the secret values, and don't change files yet.

The 30-minute version

  1. List every secret in the leaked commit.
  2. Revoke or rotate each secret key at its provider.
  3. Put the new keys in environment variables and redeploy.
  4. Check each provider’s logs and billing for misuse.
  5. Add .env to .gitignore and turn on push protection.
  6. Optionally, scrub history with git-filter-repo.

Then run the pre-launch security checklist to catch anything else before more people see the app.


Official sources used: GitHub: Remediating a leaked secret · GitHub: Removing sensitive data from a repository · GitHub: Secret leakage risks · Stripe: API keys · Supabase: API keys. Checked October 2026.

Want the whole launch path in one place?

The Ship It Kit bundles copy-paste prompts, step-by-step deploy guides and a pre-launch checklist for Lovable, Bolt, Cursor and Replit apps. One-time $19. Launch price: $19 $17 until November 14, 2026.

Get the Ship It Kit, $19$17 See what's inside

Spotted something out of date? Platforms change their screens often. The official docs linked above are always the source of truth.