Secrets
A Gemini key ships in the browser bundle and someone else runs up the bill
Cataloged October 23, 2026 · Lovable · Bolt · Cursor · Replit · v0 · Firebase
Symptom
The AI feature works in the preview and in production. Then a Google Cloud bill shows hundreds or thousands of dollars of Gemini calls your users never made, or the feature stops with Your API key was reported as leaked. The key sits in plain text in the shipped JavaScript, usually as VITE_GEMINI_API_KEY or a Vite define of process.env.GEMINI_API_KEY.
Root cause
Vite inlines any VITE_ variable, and any value passed through define in vite.config.ts, into the built JavaScript at build time. Next.js does the same for NEXT_PUBLIC_. A Gemini key used that way is readable by anyone who opens the site, and scanners crawl deployed bundles for AIza-prefixed strings and test them against the Gemini API. Google's Gemini API key guide says to treat the key like a password and never ship it in web or mobile client code, and to call Gemini through a backend instead. A second path catches Firebase and Maps apps: those browser keys were long described as safe to publish, but Truffle Security's February 2026 research found nearly 3,000 public Google API keys that also authenticated to Gemini, because the Generative Language API had been enabled in the same project and the key was unrestricted. Firebase's API key guide now says a Firebase browser key must not list the Generative Language API, and that Gemini needs its own separate, protected key. Google has since tightened this: the Gemini API rejects unrestricted standard keys, blocks long-dormant ones since May 7, 2026, and has made new AI Studio keys service-account-backed auth keys since May 28, 2026. Restricting a key does not make it safe to publish, though. A key restricted to the Gemini API but shipped in the bundle still works for whoever copies it. On the Google AI Developers Forum, a builder reported on April 21, 2026 about $2,200 of unauthorized Gemini charges in one burst after a VITE_GEMINI_API_KEY fallback leaked from a Vite bundle. Another reported on June 22, 2026 about $6,700 after a coding agent wrote a vite.config.ts define that baked the key into the frontend without warning. eserlan/Codex-Cryptica issue 2520, opened August 27, 2026, shows the aftermath: a VITE_ prefixed Gemini key returned Your API key was reported as leaked and had to be replaced.
Before and after
The before config bakes the key into the public bundle. The after config leaves it out; the browser calls your own authenticated route, and only the server reads GEMINI_API_KEY.
Before
define: { 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) }After
// vite.config.ts: no define for GEMINI_API_KEY
const res = await fetch('/api/ai', { method: 'POST', body: JSON.stringify({ prompt }) });Fix prompt
In this project only. Do not test against any other app or key. 1. Find every Gemini or Google key the client can see. Search the source for VITE_GEMINI, VITE_GOOGLE, NEXT_PUBLIC_GEMINI, NEXT_PUBLIC_GOOGLE, REACT_APP_GEMINI, GEMINI_API_KEY, GOOGLE_API_KEY, AIza, and any define block in vite.config.ts that passes process.env values to the client. Then build the app and search the build output (dist/ or .next/static/) for AIza. List each file and line. Do not print key values. 2. Move every Gemini call to the server. Create a server route or a Supabase Edge Function (or a Firebase callable function) that reads the key from a server-only secret named GEMINI_API_KEY, with no VITE_, NEXT_PUBLIC_, or REACT_APP_ prefix. The browser calls that route, never generativelanguage.googleapis.com directly. Remove the define entry and the client-side env variable. 3. Protect the route. Require a signed-in session and verify it on the server, validate the request body, cap prompt length and output tokens, and add a per-user rate limit so one account cannot burn the quota. 4. Do not commit any key. Make sure .env and .env.local are in .gitignore, and add GEMINI_API_KEY= with an empty value to .env.example. 5. Tell me, step by step, what I must do by hand in the Google consoles, because you cannot do it: - In Google AI Studio, create a new API key (new keys are auth keys), store it as the server secret, deploy, and confirm the feature works. - Then disable or delete every key that was ever in the bundle or the repo, in AI Studio or Google Cloud console under APIs & Services > Credentials. Treat it as leaked even if nothing looks wrong yet. - On the Firebase browser key, open API restrictions and confirm the Generative Language API is not in the list. - Check Billing reports for Generative Language API usage you did not expect, and add a budget alert on the billing account. 6. Rebuild and search the build output for AIza again. The only Google key left in the client should be the Firebase browser key, restricted to Firebase APIs. Do not print keys, tokens, or user data. Do not edit unrelated files.
Paste this into the coding tool that has your own project open.
2-minute self-check
These steps are for an app you own. Do not run them against someone else's project.
- On your own deployed site, open DevTools, go to Sources (or Network, then the main .js file), and search for AIza. A match whose variable or nearby code mentions Gemini, generativelanguage, or genai means your Gemini key is public.
- In your own project, run grep -rn "VITE_GEMINI\|NEXT_PUBLIC_GEMINI\|process.env.GEMINI_API_KEY" src vite.config.* next.config.* and check whether any match is read by client code or passed through define.
- In your own Google Cloud console, open APIs & Services > Credentials and check each key's API restrictions. The Firebase browser key should not list Generative Language API, and no key marked unrestricted should be in your app. Then open Billing reports, filter by Generative Language API, and look for usage you can't explain.
Common questions
Google always said Firebase API keys are safe to publish. Why is this different?
A Firebase browser key restricted to Firebase APIs only identifies your project; your Security Rules and App Check do the protecting. A Gemini key is different: it bills every call to your account. Truffle Security found nearly 3,000 public Google keys that also worked for Gemini because the Generative Language API had been enabled in the same project. Firebase's API key guide now says the Firebase browser key must not list the Generative Language API, and Gemini needs its own key kept off the client.
I restricted my key to the Gemini API. Can it stay in the frontend?
No. Restrictions stop a key from being used for other Google APIs, but anyone who copies a Gemini-only key from your bundle can still call Gemini on your bill. Google's Gemini API key guide says not to ship the key in web or mobile client code and to call Gemini through a backend. Move the call to a server route, then disable the old key, since it was already public.
Sources
- Google AI for Developers: Using Gemini API keys
- Firebase: Learn about and manage API keys
- Truffle Security: Google API keys weren't secrets, but then Gemini changed the rules
- Truffle Security: Google fixes the Gemini API key privilege escalation issue
- Google AI Developers Forum: ~$2,200 in unauthorized Gemini charges from a leaked client-side key (April 21, 2026)
- Google AI Developers Forum: agent embedded an API key in the frontend with Vite define (June 22, 2026)
- eserlan/Codex-Cryptica issue 2520 (August 27, 2026)