OpenAI or Gemini key in your frontend? Move it to an Edge Function A developer outlined a three-step remediation for web apps that ship OpenAI or Gemini API keys in client-side JavaScript: move provider calls into an Edge Function that authenticates users and reads the key from server secrets, rotate the exposed key only after the function is live, and cap per-user requests plus billing alerts to limit damage. The writeup notes that Vite inlines every VITE_ variable and Next.js does the same with NEXT_PUBLIC_, and that the OpenAI JS library requires dangerouslyAllowBrowser, which its own docs say exposes credentials. If your web app talks to OpenAI or Gemini from the browser, the API key is part of the JavaScript every visitor downloads. Anyone can copy it out and send requests that are billed to your account. Here's the order I fix it in: keep the key as a server secret, call the provider from an Edge Function that checks who is asking, then swap the leaked key for a new one. If you got here by searching, it was probably one of these: OpenAI API key exposed in frontend , dangerouslyAllowBrowser , VITE OPENAI API KEY , Gemini API key exposed in client-side code , "how to hide an API key in Lovable". Why the key ends up public - The AI call lives in a component. To get the feature working in preview, the builder calls OpenAI or Gemini directly from React, so the key ships with every page load. - It sits in a VITE or NEXT PUBLIC variable. Vite inlines every VITE variable into the client bundle at build time; Next.js does the same with NEXT PUBLIC . Whatever file it came from, the value is public. - dangerouslyAllowBrowser is on. The OpenAI JS library won't run in a browser without it, and its docs say the option exposes your secret credentials. - The key was pasted into a prompt or config file , and the builder wrote it into frontend code. Confirm it 1. Run the free Launch Check on the live site. It looks for OpenAI, Anthropic and Gemini keys in the JavaScript your site serves, and never displays the key. 2. DevTools → Network, then use the AI feature. A request going straight to api.openai.com or generativelanguage.googleapis.com means the key is in the browser. 3. Grep the code for sk-proj- , sk-ant- , AIza , dangerouslyAllowBrowser , and any VITE variable holding a key. 4. Check the provider's usage page for requests or spend you don't recognise. Fix 1: move the call into an Edge Function - Store the key as a secret, never under a VITE name. In Lovable: More Cloud Secrets . In your own Supabase project: supabase secrets set OPENAI API KEY=... . - Write an Edge Function that only accepts signed-in users, validates input, and reads the key with Deno.env.get . For Gemini, call the generateContent endpoint the same way, with the key in the x-goog-api-key header. - In the page, call the function with supabase.functions.invoke and remove both the key and the provider SDK from the frontend. Fix 2: replace the leaked key Do this only after the function is live, or the new key leaks the same way. - OpenAI: create a new key on the project's API keys page, save it as the server secret, then delete the leaked one. - Gemini: create a new key in Google AI Studio keys created there since 28 May 2026 are auth keys, limited to the Gemini API by default , save it as the secret, then delete the old key. Restrict any key you keep to the Gemini API. - Deleting a key is final, so move every function and server to the new key first. Fix 3: limit the damage meanwhile - Set a budget alert in the provider's billing settings so unexpected spend reaches you the same day. - Cap requests per user inside the function for example a daily count stored in a table so one account can't drain the budget. - Note any usage you don't recognise, with dates, in case you need to raise it with the provider. Prove it worked - Launch Check again: no AI keys found. - With DevTools open, the browser calls your function, never the provider. - Calling the function while signed out is refused. - The provider dashboard shows the leaked key deleted. Keep it from coming back - Provider keys live only in server secrets: never in VITE / NEXT PUBLIC , never with dangerouslyAllowBrowser . - When you ask the builder for an AI feature, ask for it "through an Edge Function, with the key stored in Secrets" from the first prompt. - Turn on GitHub secret scanning, and run the Launch Check after every publish. Quick answers Can an env variable hide the key? Not in the frontend. Vite writes VITE values into the JavaScript at build time. Only a server or Edge Function keeps it secret. Is a Gemini key safe if it's restricted to my domain? Not for a key that can spend money. A website restriction checks the Referer header, which a script outside a browser can set to anything. Google's guidance is to call the Gemini API from your server. Does Lovable's built-in AI have this problem? Not by design: it uses LOVABLE API KEY , a secret only Edge Functions can read, and Secrets offers a Rotate action for it. The risk is a key you add yourself and call from the page. Will the provider disable a leaked key for me? Don't count on it. Until a provider acts, every request made with your key is billed to you. Sources: OpenAI API reference Authentication, JavaScript library browser use , Google AI for Developers Using Gemini API keys , Vite Env variables and modes , Lovable Secrets , Supabase Edge Function secrets, Securing Edge Functions . Links are on the original page. I first wrote this up for our fix library at StacksMesh: OpenAI or Gemini API key exposed in your app's code https://stacksmesh.com/fix/ai-api-key-exposed-in-frontend?utm source=devto&utm medium=article&utm campaign=BR-0002 , with links to every official source. If you want to check your own live app for exposed keys, the free Launch Check https://stacksmesh.com/tools/launch-check?utm source=devto&utm medium=article&utm campaign=BR-0002 does it in about a minute. I wrote this with AI assistance and reviewed it myself.