You just found a secret key in your GitHub repo, a frontend bundle or a screenshot you posted. The good news: rotating it is usually a 15 minute job. The bad news: until you do, anyone who copied it can use it.
This guide covers the providers vibe-coded apps use most: Supabase, OpenAI and Stripe. The pattern is the same everywhere.
The general rule: new key, deploy, then revoke
To rotate without downtime:
- Create a new key at the provider.
- Update the environment variable in your host (Vercel, Netlify, Cloudflare, Replit, Supabase Edge Function secrets) and in your local
.env. - Redeploy so running code picks up the new value. Most hosts only read env vars at build or start time.
- Revoke the old key.
- Check logs and billing for anything you didn't do.
If you suspect active abuse, for example an OpenAI bill climbing in real time, skip ahead and revoke first. A few minutes of downtime is cheaper than the alternative.
Don't spend your first half hour rewriting git history. Removing the key from your repo doesn't stop someone who already has it. Rotate first, clean up later. If you haven't yet checked for other leaks, see how to check if your API keys leaked on GitHub.
Supabase
Supabase projects have used two kinds of API keys over time, so first check which one you have.
Newer keys: publishable and secret
Keys that start with sb_publishable_ and sb_secret_ are the newer format. These can be rotated individually:
- In the Supabase dashboard, open Project Settings > API Keys.
- Create a new secret key.
- Put it in your server environment (Edge Function secrets, your backend host) and redeploy.
- Delete the leaked secret key.
Your publishable key is meant to be public, so it usually doesn't need rotating. What protects your data is RLS.
Legacy keys: anon and service_role JWTs
The legacy anon and service_role keys are long JWTs (they start with eyJ). They're signed with your project's JWT secret, so you can't revoke just one. Your options:
- Recommended: switch to the newer publishable and secret keys, update your app to use them, then disable the legacy keys in the dashboard.
- Or rotate the project's JWT secret, which issues new anon and service_role keys. This invalidates both keys at once and can sign users out, so plan for it.
Check the current Supabase docs for the exact flow in your project, as the dashboard has been changing.
After rotating Supabase keys
- Review the Logs in the dashboard for API requests you don't recognize, especially using the service role.
- Check your tables for unexpected rows, deleted data or changed
role/is_adminvalues. - Run through the Supabase RLS checklist. A leaked service role key bypasses RLS, but a leaked anon key with weak RLS is just as bad.
Also look at where the key was used. A service role key should only live in server code. If your AI tool put it in a React component, the fix is to move that query behind an Edge Function or API route, not just to rotate.
OpenAI (and other LLM providers)
LLM keys get abused fast because they can be resold or used to run up compute.
- Go to platform.openai.com > API keys (keys may live under a specific project).
- Create a new key, ideally a project key scoped to only this app.
- Update your server env var and redeploy.
- Revoke the leaked key.
- Open the Usage page and look for spikes, models you don't use, or activity at odd hours.
- Set a budget and usage limits for the project so a future leak has a ceiling.
If you see charges you didn't make, contact OpenAI support. The same steps apply to Anthropic, Google AI Studio, Groq, Replicate and similar services: rotate, check usage, set limits.
An OpenAI key should never be in frontend code at all. If your app calls OpenAI from the browser, move the call into an API route, add auth, and add rate limiting. Our guide on rate limiting OpenAI API routes shows how.
Stripe
- In the Stripe Dashboard, go to Developers > API keys.
- Next to the leaked secret key, choose Roll key. Stripe lets you set an expiry on the old key (for example 1 hour) so you have time to deploy the new one.
- Update
STRIPE_SECRET_KEYon your server and redeploy before the old key expires. - Check Developers > Logs for API requests you didn't make, and look for unexpected refunds, payouts or changes to your account.
Consider using restricted keys (rk_) that only have the permissions your app needs.
If your webhook signing secret (whsec_) leaked, roll it from the webhook endpoint's settings. Someone with that secret can forge webhook events, which matters a lot if your webhook unlocks paid features. See Stripe webhook signature verification in Next.js.
Other common keys
- Resend, SendGrid, Postmark: rotate and check sent mail logs. A leaked email key can be used to send spam from your domain.
- Database URLs: change the database user's password, not just the env var.
- GitHub tokens: revoke under Settings > Developer settings, and check the security log.
- Firebase service account JSON: delete the key in Google Cloud IAM and create a new one. The Firebase web
apiKeyis not a secret, but service account keys are.
Clean up the source of the leak
Once the new keys are live and the old ones are dead:
- Remove the hardcoded value from your code and read it from an environment variable instead.
- Make sure
.envfiles are in.gitignore. - If it was in a
NEXT_PUBLIC_orVITE_variable, rename it and move the code that uses it to the server. See exposed API keys in NEXT_PUBLIC_ and VITE_. - Optionally remove it from git history with
git filter-repo, knowing that forks and clones keep their copies. - Turn on GitHub push protection so the next one is blocked before it's pushed.
A quick rotation checklist
- New key created
- Env var updated everywhere (production, preview, local)
- App redeployed and tested
- Old key revoked or expired
- Logs and billing reviewed
- Usage limits set where the provider supports them
- Secret moved out of code and client bundles
Catch the next one earlier
Leaks are rarely a one-off in AI-built apps, because the same habit that leaked one key tends to leak others. PreflightPR looks for hardcoded secrets and secrets in public env vars, along with missing RLS and unprotected routes. Run a free scan on a public repo to check yours. It's an automated review, not a guarantee, so keep rotating anything you're unsure about.