©Code byMilanParmar

Lovable app API key exposed: what to do in the next hour

How to confirm a key is in your app's code, GitHub, or a Lovable chat, the order to work in, how to rotate OpenAI, Anthropic, Stripe, Supabase, and Resend keys, which keys are safe in a browser, where secrets belong instead, and how to stop it happening again.

13 min read

If an API key from your Lovable app is exposed, rotate it at the provider first, before touching the code. A key in the browser bundle, a GitHub repository, or a Lovable chat has already been copied, so deleting it changes nothing. Then find where the app used it, check the provider's usage page, and set a spend limit.

A secret key is a password for a service your app pays for or trusts: OpenAI or Anthropic for AI features, Stripe for payments, Resend for email, and Supabase's secret key, which reaches your whole database. Apps built with Lovable, Bolt, and similar tools run mostly in the visitor's browser, so when the AI needs one of those keys it often puts it where the browser can read it. This guide is for the founder who just found one there, or was told by GitHub, OpenAI, or a security scan. It covers secret keys only. The Supabase public key is meant to be in the browser; what protects it is covered in the Supabase RLS checklist.

How to tell if an API key is exposed in your Lovable app

Check the published app, not the Lovable preview, because that is what strangers can load. Ten minutes covers the four places a key ends up.

Where a leaked key shows up, and how to look
WhereHow to checkWhat you're looking for
The browser bundleOpen the live app in Chrome, open developer tools, go to Sources, and search all files (Cmd+Option+F on a Mac, Ctrl+Shift+F on Windows)sk- (OpenAI), sk-ant- (Anthropic), sk_live_ and rk_live_ (Stripe), re_ (Resend), sb_secret_ or service_role (Supabase)
The network tabDeveloper tools, Network, then use the AI feature or checkout. Click a request and read its headersRequests going straight from the browser to api.openai.com, api.anthropic.com, or api.stripe.com with a secret in the Authorization header, or a Supabase request whose apikeyheader isn't your publishable key
GitHubOpen the synced repository, then Security, then Secret scanning alerts. Also search the repo for the prefixes aboveAn alert, or a key in .env, src/, or supabase/functions/
The Lovable chatScroll the project's chat history and search it for the prefixes aboveAny key you or a teammate pasted into a prompt, on any project that was ever public

The network tab is the one most guides skip, and it catches keys the bundle search misses: a key loaded at runtime from a config table, or passed through a helper function under a different name. If a request to a provider leaves the browser with anything other than a publishable key, the key is exposed, whatever the code looks like.

Sometimes the provider tells you first. OpenAI's help centre says that when it detects a key on the public internet, or inside an app in an app store, it disables the key immediately. Stripe says it alerts the owner and may invalidate the key when it finds a secret key exposed. GitHub's secret scanning notifies partner providers, and its supported patterns list OpenAI, Anthropic, Stripe, Supabase secret keys, and Resend as partners. An email like that is confirmation, not a false alarm: skip to rotating.

What to do first when an API key is leaked

The order matters more than speed. People who start by editing code leave the old key working for hours while they do it.

  1. Rotate the key at the provider.Create a new one, then revoke or expire the old one. Supabase's rotation guide says to fix the cause first so the new key doesn't leak the same way, which is right for Supabase because its keys sit in a config file; for an OpenAI or Stripe key that is burning money, revoke now and fix after. Per-provider steps are in the next section.
  2. Find every place the app used it. Search the code for the variable name, not just the value. Keep a list: the AI chat feature, the email sender, the admin page. Each one will break until it gets the new key from a safe place.
  3. Put the new key where the browser can't read it. Lovable Secrets or Supabase Edge Function secrets, never the .env file. How that works is in the section on where secrets belong.
  4. Check usage and billingon the provider's dashboard, and set a spend limit while you are there. Details below.
  5. Ask what the key could reach.An OpenAI key spends your money. A Stripe secret key can read customers and issue refunds. A Supabase secret key can read and change every table, which turns a leak into a possible data breach: check the logs for requests you didn't make, and if real customer data may have been read, talk to a lawyer about your duty to notify.
  6. Write down what happened: which key, when it went into the code, when it was rotated, and what the usage page showed. Your bank, your insurer, or a customer's security review may ask.

How to rotate OpenAI, Anthropic, Stripe, Supabase, and Resend keys

Every provider does this slightly differently. The steps below are from each one's own docs, as of October 2026.

Rotating a leaked key, by provider
ProviderHow to rotateWorth knowing
OpenAIAPI keys page: create a new key, delete the compromised one, review usage, contact support with the detailsProject keys can carry an expiry date; OpenAI recommends creating the replacement before revoking the old one
AnthropicAPI keys page in the Console: create a new key, then Delete API key on the old oneAnthropic's guidance is to revoke immediately if you suspect a key is compromised, and to rotate on a schedule such as every 90 days
StripeAPI keys page, the menu on the key, Rotate key. Choose Now, or a grace period of up to 7 days if the old key must keep working while you deployStripe recommends restricted keys (rk_) over secret keys for new code, so the replacement should be one with only the permissions the app uses
Supabase secret key (sb_secret_)Settings, API Keys: create a new secret key, replace the old one everywhere, then delete itSupabase suggests one secret key per backend component, so a future leak only costs you one rotation
Supabase legacy service_role keyCreate a secret key for your functions and a publishable key for the browser, move everything over, then deactivate the legacy keysSupabase's guide says to also revoke the legacy JWT secret, since the old key is a token signed by it, and never to re-enable the legacy keys
ResendCreate a new key, delete the old oneKeys take a permission and an optional domain restriction, so the replacement can be limited to sending from one domain

On Lovable Cloud, the Supabase keys are managed for you and there's no Supabase dashboard; the third-party keys are still yours to rotate in each provider's dashboard, and the new values go into the project's Secrets.

Check provider usage and billing after a key leak

Open the usage page the same day, then again a week later. A key scraped from a public repo is often used quietly for a while before the bill arrives. In a 2023 thread on OpenAI's developer forum titled "Something hacked my account", the founder only noticed from over $100 of charges in a single day, and the advice that held up was the same as here: rotate the key, keep it out of the front end, and set a low spend limit.

OpenAI and Anthropic

On OpenAI, the usage page shows spend by day and by project. Set spend alerts (OpenAI suggests thresholds such as 90% and 95% of a monthly budget) and a hard limit at the organisation or project level. OpenAI's spend limits guide says alerts only notify, while a hard limit makes requests fail with a 429 error once the limit is reached, so pick a hard limit that would hurt but not stop the business. On Anthropic, the Billing page has spend limits that its help centre describes as a safeguard against leaked keys and errant scripts. Separate keys per feature make the usage page readable: you can see which feature spent what.

Stripe

Stripe's advice for a compromised key is to review that key's request logs in Workbench for charges, refunds, or connected accounts you don't recognise. Then attach an access policy to every live key, which restricts where it can be used from and, in Stripe's words, notifies you of any unauthorised access.

Supabase

There is no bill to watch, which is worse: a secret key reads your database for free. In the Supabase dashboard, open Logs, filter API Gateway, and look at the window between the key going into the code and the rotation for requests from addresses that aren't your app, especially reads of whole tables. Keep a copy of what you find. If real users' data may have been read, the next steps are the same as after a hack, in what to do after a breach: who to tell, and when.

Which API keys are safe in the browser, and which never are

A key is safe in the browser only when the provider designed it to be, which means it can't spend money, read private data, or bypass your access rules on its own. Keep this list.

Keys by provider: public or secret
KeyStarts withIn the browser?What it can do if leaked
Supabase publishable (older: anon)sb_publishable_, eyJYes, by designOnly what your RLS policies allow
Supabase secret (older: service_role)sb_secret_, eyJNeverRead and change every table; bypasses RLS entirely
Stripe publishablepk_live_, pk_test_Yes, by designCreate payment tokens; no charges, no account data
Stripe secret or restrictedsk_live_, rk_live_NeverCharges, refunds, customer data; restricted keys less
Stripe webhook signing secretwhsec_NeverForge "payment succeeded" events to your app
OpenAIsk-, sk-proj-NeverSpend on your account until the limit or the bill
Anthropicsk-ant-NeverSame: usage billed to you
Resendre_NeverSend email as your domain, which gets it blacklisted
Lovable AI gateway keyLOVABLE_API_KEYNeverRuns the AI features Lovable built into your app; treat it like the others

Two rows surprise people. The Supabase publishable key is fine in the browser, and the fact that it starts with eyJon older projects doesn't make it secret; the older service_role key starts the same way, which is why you have to check which one the app holds, not just the prefix. And the Stripe webhook secret isn't an API key at all, but it's just as private: with it, anyone can tell your app a payment went through. How the webhook itself should verify events is in fixing a Stripe webhook in a Lovable app.

Where secrets belong in a Lovable app

The rule is one sentence: a secret key is only ever read by code that runs on a server, and the browser calls that code. In a Lovable app, that code is a Supabase Edge Function, whether the backend is Lovable Cloud or your own Supabase project.

Lovable Secrets and Edge Function secrets

Lovable's docs describe Secrets as encrypted values that are injected into your Edge Functions at runtime and never reach the browser. You add them under More, then Settings, then Secrets, or when Lovable asks for one through its secure input. Lovable's Supabase integration docs say the values are stored in your Supabase project, where the functions read them, and never appear in your app's code or repository. If you manage the Supabase project yourself, the same values live under Edge Function Secrets in the Supabase dashboard, or are set with supabase secrets set, and Supabase's docs say functions read a new value immediately without a redeploy.

VITE_ means public

Lovable apps are built with Vite, and Vite's docs are blunt: variables prefixed with VITE_are exposed in the client-side code after bundling and should not contain sensitive information such as API keys. Lovable's Secrets docs say the same from the other side: VITE_ values live in the project's .env file, not in Secrets, and are embedded in the bundle, so they are meant to be public. The name is the tell. If an AI prompt produced VITE_OPENAI_API_KEY or VITE_SUPABASE_SERVICE_ROLE_KEY, the key is in the bundle, whatever else the code does.

What the fixed version looks like

Compare the AI feature in your app against this shape: the browser sends the user's question to a function; the function checks the user is logged in, reads the key from the environment, and calls the provider.

supabase/functions/ask/index.ts
import { createClient } from "npm:@supabase/supabase-js@2";

Deno.serve(async (req) => {
  // Only logged-in users may spend your OpenAI budget.
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_ANON_KEY")!,
    { global: { headers: { Authorization: req.headers.get("Authorization")! } } },
  );
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return new Response("Unauthorized", { status: 401 });

  const { question } = await req.json();
  if (typeof question !== "string" || question.length > 2000) {
    return new Response("Bad request", { status: 400 });
  }

  // The key is a secret set in Lovable or Supabase, never a VITE_ variable.
  const res = await fetch("https://api.openai.com/v1/responses", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${Deno.env.get("OPENAI_API_KEY")}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ model: "gpt-5-mini", input: question }),
  });
  if (!res.ok) return new Response("Upstream error", { status: 502 });

  return Response.json(await res.json());
});

The browser then calls it with supabase.functions.invoke("ask", { body: { question } }) and never sees the key. The login check and the length cap matter: without them, you've hidden the key and left the spending open to anyone who finds the function's URL.

Why Lovable apps leak API keys

Knowing how the key got there is how you stop the next one. In the apps I'm sent, it's one of four routes.

  • The prompt handed it over."Add my OpenAI key sk-… and make the chat work" is the fastest way to get a working feature and a leaked key. Lovable now detects a key pasted into chat and saves it as a project secret instead, but a key the AI was asked to put in a component goes where it was told.
  • The AI used the service_role key to get past a permissions error. A read came back empty because of RLS, and the fix it reached for was the key that bypasses RLS, in the browser. The empty result was the right outcome; the fix was a policy, not a stronger key.
  • The .env file is in the repository. Lovable's docs say not to add .env to .gitignore, because previews and builds need the VITE_ values in it. That is fine while it holds only public values. The moment someone adds a secret to it, the secret is in GitHub, in every commit from then on.
  • The chat was readable. The Stack reported on 21 April 2026 that Lovable had, in its own words, accidentally re-enabled access to chats on public projects in February 2026 while unifying permissions, after making projects and chats private by default in December 2025. Before May 2025, public projects showed chats and code by design. Any key pasted into a chat on a project that was public at any of those times should be treated as copied.

How to stop API keys leaking again

None of this takes a developer. It takes an hour, once.

If you've moved the app off Lovable, the same list applies with your host's environment variables standing in for Lovable Secrets; the move itself, secrets included, is in moving a Lovable app to your own hosting.

When to get help with a leaked API key

Do it yourself if the key is an OpenAI or Resend key, the app has one AI feature, and you can see where to move it. Get help if the leaked key is Supabase's secret key or a live Stripe key, if the app has paying users, if the usage page shows activity you didn't cause, or if you rotated the key and the app now has three broken features and you're not sure which prompts to write.

Finding every key in the bundle and moving each one behind a function is part of my Launch-Ready Review, which costs $900–1,500 and takes 3–5 days; the fixes it lists are usually a Production Sprint, and what each step costs is in the cost to fix a vibe-coded app. The key that would cost the most on The Chat Company, the chatbot SaaS I rebuilt from an AI-generated demo, is the OpenAI key behind its answer engine, which now handles 100K+ messages a day for 30+ businesses. The widget that sits on each customer's website never holds it: every answer goes through the server, which is the only place the key exists.

If you've found a key and want to know how bad it is, send me the app's link and the key's first few characters, never the whole key, and I'll tell you what it could reach and what to rotate first.

Sources

  1. Secrets: encrypted, injected into Edge Functions, never reach the browser, Lovable Docs
  2. Supabase integration: secrets stored in your Supabase project, Lovable Docs
  3. Security: quick scan, deep scan, and keys pasted into chat, Lovable Docs
  4. Understanding API keys: publishable and secret keys, Supabase Docs
  5. Rotate a leaked or compromised API key, Supabase Docs
  6. Edge Function secrets and default environment variables, Supabase Docs
  7. Env variables: VITE_ variables are exposed in client code, Vite
  8. Keeping your OpenAI account secure: leaked keys are disabled, OpenAI Help Center
  9. Spend limits: alerts and hard limits, OpenAI Developers
  10. API key best practices: revoke a compromised key, Claude Help Center
  11. API keys: key types, rotating with a grace period, access policies, Stripe Docs
  12. Protecting against compromised API keys, Stripe Support
  13. API keys: permission and optional domain restriction, Resend Docs
  14. About secret scanning: full history, partners notified, GitHub Docs
  15. Supported secret scanning patterns: partner providers, GitHub Docs
  16. About push protection, GitHub Docs
  17. Lovable spews user data, says chats were meant to be publicly accessible, The Stack
  18. Something hacked my account: unexpected API charges, OpenAI Developer Community
Milan Parmar

The developer

Freelance full-stack developer in Bengaluru with 5+ years of experience. I redesign websites, take over broken apps, and build SaaS products, with 18 case studies to show for it.

FAQs

Is it enough to delete the API key from my Lovable app's code?

No. Anyone who loaded the app while the key was in it may have a copy, and GitHub's secret scanning reads your entire commit history, not just the latest version. Removing the key stops the next visitor seeing it, and nothing else. Rotate the key at the provider so the old one stops working, then remove it from the code and fix where it was used.

My key was pasted into a Lovable chat. Was it exposed?

Treat it as exposed. The Stack reported in April 2026 that a February 2026 change at Lovable accidentally re-enabled access to chats on public projects, and that until May 2025 public projects showed chats and code by design. If a key was ever pasted into a chat on a project that was public at any point, rotate it, and check that provider's usage page.

Does Lovable's security scan find exposed API keys?

Partly. Lovable's docs say the quick scan that runs on publish covers database rules, dependencies, and MCP exposure, not secrets. The deep scan reviews your application code and includes leaked secrets and credentials. Lovable also detects a key pasted into chat and steers you to Secrets. None of these stop a key that the AI wrote into a component, so still check the bundle yourself.

Do I need to rewrite my GitHub history to remove the key?

Not for safety, once the key is rotated: a dead key in old commits can't be used. Rewriting history is worth it only to stop scanners flagging the repo again, and it's disruptive because every collaborator and Lovable's sync must re-clone. Rotate first, remove the key from the current code, and leave the history alone unless a client's security review insists.