Guides · VibeVigil
How to Rotate a Leaked API Key in a Lovable, Bolt or Cursor App
What to do after an API key leaks from a vibe-coded app: revoke and rotate OpenAI, Anthropic, Stripe and Supabase keys, remove them from the browser and GitHub history, move secrets to Edge Functions or server env, check for abuse, and paste a fix prompt into your coding agent.
Not sure if your app is still exposing keys? Run a free scan first.
VibeVigil checks your live app for exposed keys, Supabase tables without RLS, missing security headers, SSL problems and downtime. Every issue comes with a ready-to-paste prompt for your coding agent.
Scan my app for free → (only scan apps you own)
Finding a secret key in your Lovable, Bolt, v0 or Cursor app is scary. Deleting the line of code is not enough. Anyone who already copied the key can keep using it until you turn it off.
This guide is the "what to do next" checklist: revoke the old key, put a new one in the right place, clean up the leak, and check for abuse. If you still need to find the leak, start with How to Check if Your Vibe-Coded App Is Leaking API Keys.
The short answer
- Stop the bleeding. Create a new key, put it only on the server, then revoke or delete the old key.
- Fix the cause. Remove the secret from browser code,
VITE_/NEXT_PUBLIC_variables, public files and GitHub history. - Check for abuse. Review usage logs and bills for the last few days.
- Verify. Search your live site again. The secret should be gone.
Do not put a new secret into the same place that leaked. Fix the path first, then rotate.
What "rotate" means
Rotate means: make a new key, switch your app to it, then turn off the old one. Removing the key from your code alone leaves the old key working for whoever already has it.
Providers say the same thing:
- OpenAI: if you believe a key leaked, rotate it immediately from the API Keys page and update your apps. (OpenAI: Best practices for API key safety)
- Anthropic: revoke a compromised key from the Claude Console API keys page (Disable or Delete). (Anthropic: API key best practices)
- Stripe: treat any exposure as a compromise and rotate right away. (Stripe: Best practices for managing secret API keys)
- Supabase: create a new secret key, replace the leaked one everywhere, then delete or deactivate the old key. (Supabase: Rotate a leaked or compromised API key)
Step 1: Decide which key leaked
| Key | Action |
|---|---|
OpenAI (sk-...) | Rotate in the OpenAI API keys dashboard |
Anthropic / Claude (sk-ant-...) | Disable or delete in Claude Console → API keys |
Stripe secret (sk_live_... / sk_test_...) or restricted (rk_...) | Rotate in Stripe Dashboard → API keys |
Supabase secret (sb_secret_...) or legacy service_role | Create a new secret key, swap it in, then delete / deactivate the old one |
| Email (Resend, SendGrid, Postmark) | Revoke in that provider's dashboard and create a new key |
Supabase publishable / anon, Stripe publishable (pk_...) | These are meant for the browser. You usually do not rotate them for a "leak" — but if a secret key was mixed in, rotate that secret |
Supabase publishable (or legacy anon) keys are safe to expose online; secret and service_role keys bypass Row Level Security and must stay on the server. (Supabase: API keys)
Step 2: Rotate by provider
Do this for each leaked secret. Prefer: create new → update server → verify app works → revoke old.
OpenAI
- Open the OpenAI API keys page.
- Create a new project key (set an expiration if you can).
- Put the new value only in server env (Edge Function secrets, Next.js server env, host secrets). Never
VITE_orNEXT_PUBLIC_. - Redeploy and confirm chat / API calls still work.
- Delete or revoke the old key.
- On the Usage page, look for spikes you did not cause. Set a spend limit if available. (OpenAI: Best practices for API key safety)
Anthropic (Claude)
- Open Claude Console → API keys.
- Create a new key and store it in server secrets as
ANTHROPIC_API_KEY(or your host's secret name). - Redeploy and test.
- On the old key's menu, choose Disable API key (reversible) or Delete API key (permanent). If you suspect active abuse, delete right away. (Anthropic: API key best practices)
- Check Usage and Logs for odd traffic.
Stripe
- In the Stripe Dashboard, open API keys.
- Open the overflow menu on the leaked secret or restricted key → Rotate key.
- For a confirmed leak, expire the old key Now (or use a short grace window only if you need zero downtime while you redeploy). Copy the new key once. (Stripe: API keys)
- Store it in server env only. Prefer a restricted key with the smallest permissions your app needs. (Stripe: Best practices for managing secret API keys)
- Review recent charges, customers, payouts and Developers → Logs for activity you do not recognize.
Supabase secret / service_role
Supabase's own rotation guide is the source of truth. Summary:
- Fix how the key leaked before you ship a new one (see Step 3). Otherwise the new key will leak the same way. (Supabase: Rotate a leaked or compromised API key)
- In Settings → API Keys, create a new secret key (
sb_secret_...). - Put that new secret only in backend places: Supabase Edge Function secrets, server routes, webhooks — never in the browser, never as
VITE_/NEXT_PUBLIC_. - If the leak was a legacy
service_roleJWT, also move browser clients to a publishable key. Deactivating legacy keys turns off both legacyanonandservice_role. - After every component uses the new key, delete the leaked secret key, or deactivate legacy keys. If someone is actively abusing the key, retire it immediately even if some jobs break until you finish the swap.
- Check Auth logs, Database / API logs and Storage for reads or writes you did not expect.
Regenerate carefully: the browser should keep using publishable / anon only. The secret / service_role key is for trusted server code. Mixing them up is how many vibe-coded apps get into trouble.
Step 3: Remove the secret from places that leak
Rotating without cleaning these up will burn the new key too.
Browser bundles and public prefixes
Vite copies any VITE_ variable into client code. Next.js does the same for NEXT_PUBLIC_. (Vite: Env Variables and Modes, Next.js: Environment Variables)
Rename secrets so they have no public prefix, for example:
# Bad — ends up in the browser
VITE_OPENAI_API_KEY=...
NEXT_PUBLIC_STRIPE_SECRET_KEY=...
VITE_SUPABASE_SERVICE_ROLE_KEY=...
# Good — server / Edge Function only
OPENAI_API_KEY=...
STRIPE_SECRET_KEY=...
SUPABASE_SECRET_KEY=...Move calls behind a server
Pattern that works in Lovable, Bolt, v0 and Cursor apps:
- The browser calls your endpoint (Supabase Edge Function, Next.js route, or host serverless function).
- That server function reads the secret from its env and calls OpenAI / Anthropic / Stripe / Supabase admin APIs.
- The browser never sees the secret.
GitHub history
Deleting a file does not erase old commits. If the repo is public (or was public), treat any committed secret as leaked. (GitHub: Removing sensitive data from a repository)
- Make sure
.envand.env.*are in.gitignore. - Rotate the key (Step 2) even if you will later rewrite history.
- Turn on secret scanning and push protection in the repo settings. (GitHub: About secret scanning)
- If needed, follow GitHub's guide to purge the secret from history (for example with
git filter-repo), then force-push only if you understand the impact on collaborators.
Public .env on the host
Try https://your-app.com/.env on your own site. If you see key text, block that file on the host, rotate every key inside it, and redeploy.
Step 4: Check logs for abuse
After rotation, spend 10–20 minutes on each provider you touched:
- OpenAI / Anthropic: Usage and request logs — sudden token spikes, models you do not use, traffic at odd hours.
- Stripe: Unexpected charges, new customers, webhook failures, API errors from unknown IPs.
- Supabase: Large table reads, new users you did not create, Storage downloads, Edge Function invocations.
- Your host bill: Bandwidth or function spikes.
If you see clear abuse, keep the old key revoked, document what you saw, and contact that provider's support from their official help site. Do not paste live secrets into tickets.
Step 5: Confirm the leak is gone
- Open your live app → F12 → Sources → search for
sk-,sk_live,sk-ant-,sb_secret_,service_role,OPENAI,ANTHROPIC,STRIPE_SECRET. - Confirm no secret appears in Network request headers from the browser.
- Confirm env names for secrets do not start with
VITE_orNEXT_PUBLIC_. - Re-test the product path that needs the key (chat, checkout, admin job) so you know the new key works.
Want a second pass on the live site? Run a free VibeVigil scan → (only scan apps you own)
Paste this prompt into your coding agent
Works in Lovable, Bolt, v0, Cursor or Claude Code:
A secret API key may have leaked from this app. Help me rotate safely and stop future leaks. Don't delete user data.
1. Search the whole project (source, .env*, config, Edge Functions, server routes) for secret keys:
OpenAI, Anthropic/Claude, Stripe sk_/rk_, Supabase service_role or sb_secret_, Resend/SendGrid, and names with SECRET, TOKEN or PASSWORD.
2. List every VITE_ and NEXT_PUBLIC_ variable. Tell me which ones hold secrets (those are leaks).
3. Move every secret to server-only code (Supabase Edge Function secrets or a server route). The browser must call my server; only the server may call paid APIs or use the Supabase secret key.
4. Keep the frontend on the Supabase publishable/anon key only. Never ship service_role or sb_secret_ to the client.
5. Ensure .env and .env.* are in .gitignore and are not publicly served.
6. Give me a checklist of keys to rotate, with the exact dashboard for each (OpenAI, Anthropic, Stripe, Supabase, email).
7. Tell me what to check in each provider's usage/logs for abuse after rotation.
8. Explain each code change in plain English and how to test that the app still works with the new server-side keys.FAQ
I already deleted the key from my code. Am I done? No. Revoke or rotate it in the provider dashboard. Old deploys, GitHub history and anyone who copied it can still use the old value.
Should I rotate the Supabase anon / publishable key too? Usually no — it is designed for the browser and is limited by RLS. If you leaked service_role / sb_secret_, rotate that secret. If you deactivate legacy keys as part of that rotation, you must move clients to the new publishable key first. (Supabase: Rotate a leaked or compromised API key)
Can I put the new key in a VITE_ variable "just for now"? No. That puts the new key in the browser again. Use a server function from the start.
OpenAI or Anthropic already emailed me that a key was disabled. Good — treat it as confirmed exposure. Create a replacement on the server path above, then confirm your app works without the disabled key.
Sources
- OpenAI Help, Best practices for API key safety: https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
- Anthropic Help, API key best practices: https://support.claude.com/en/articles/9767949-api-key-best-practices-keep-your-keys-safe-and-secure
- Stripe docs, API keys: https://docs.stripe.com/keys
- Stripe docs, Best practices for managing secret API keys: https://docs.stripe.com/keys-best-practices
- Supabase docs, Rotate a leaked or compromised API key: https://supabase.com/docs/guides/security/rotate-leaked-api-keys
- Supabase docs, API keys: https://supabase.com/docs/guides/getting-started/api-keys
- Vite docs, Env Variables and Modes: https://vite.dev/guide/env-and-mode
- Next.js docs, Environment Variables: https://nextjs.org/docs/app/guides/environment-variables
- GitHub docs, Removing sensitive data from a repository: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository
- GitHub docs, About secret scanning: https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning
Scan your app free. It checks the public page of a site you own. No account.