Dev.to Security 🔐 Cybersecurity 👁 0 📖 3 min read

Your AI Studio app export ships your Gemini key. Check dist/ for AIza

Google AI Studio's Build mode is a fast way to get a working Gemini app. Export it, deploy it to Vercel, Netlify or Firebase Hosting, and it works. The catch: depending on the template, the exported vite.config.ts can c

Google AI Studio's Build mode is a fast way to get a working Gemini app. Export it, deploy it to Vercel, Netlify or Firebase Hosting, and it works.

The catch: depending on the template, the exported vite.config.ts can copy your GEMINI_API_KEY into the JavaScript that every visitor downloads. People on the Google AI Developers Forum have reported four-figure bills from exactly this.

Check in 30 seconds

npm run build
grep -r "AIza" dist/

Any match means the key is public. You can also open your live site, search the JavaScript files in DevTools (Sources tab) for AIza, or watch the Network tab for calls to generativelanguage.googleapis.com that carry your key.

Why it happens without a VITE_ prefix

Vite only exposes VITE_ variables to client code by default (Vite env docs). A define block bypasses that: Vite replaces the expression with a literal value at build time (define option). Look for this pattern:

// vite.config.ts
import { defineConfig, loadEnv } from "vite";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, ".", ""); // empty prefix: loads EVERY variable
  return {
    define: {
      // Replaced with the key's literal value in the browser bundle
      "process.env.GEMINI_API_KEY": JSON.stringify(env.GEMINI_API_KEY),
    },
  };
});

Fix it, in this order

1. Rotate the key now

If grep found it, assume it's been copied. Delete the key in AI Studio, create a new one, and check the project's usage for the period the site was live. (If you need it, here's a checklist for a leaked key, including what evidence to keep for a billing dispute.)

2. Remove the define entries

Delete process.env.GEMINI_API_KEY (and process.env.API_KEY, if present) from define, remove the client code that reads them, rebuild, and run the grep again. It must return nothing.

3a. Call Gemini from a server function

Keep the key in a server-only variable (no VITE_ prefix) and have the browser call your function. A Vercel function in a Vite project:

// api/gemini.ts. Set GEMINI_API_KEY in the project's env, no VITE_ prefix.
export async function POST(request: Request) {
  const { prompt } = await request.json();
  const upstream = await fetch(
    "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:generateContent",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-goog-api-key": process.env.GEMINI_API_KEY!,
      },
      body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
    }
  );
  return new Response(upstream.body, {
    status: upstream.status,
    headers: { "Content-Type": "application/json" },
  });
}

Netlify functions and Cloud Run handlers can use the same Request/Response objects. Add your own rate limiting or auth: anyone can call this endpoint.

3b. Or skip the backend with a scoped token

Disclosure: I build KeyVault Edge, so weigh this accordingly.

If you don't want to run a server, KeyVault Edge holds the real AIza key at the edge and gives your app a kvt_ token locked to your domain, to the generateContent endpoint, to a requests-per-minute limit and to a monthly cost cap:

const res = await fetch(
  "https://proxy.keyvaultedge.com/v1beta/models/gemini-3.5-flash:generateContent",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-KV-Token": import.meta.env.VITE_KVE_TOKEN, // kvt_..., not your AIza key
      "X-KV-Target": "https://generativelanguage.googleapis.com",
    },
    body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
  }
);

To be clear about the trade-off: the token is visible in your bundle. What you get is that the real key never is, browsers on other sites are refused, the token's limits cap what a copy can spend, and you can revoke it in one click without rotating the Gemini key. A copied token can still be replayed from a server that fakes the Origin header, up to those limits. There's a free sandbox that shows the allow/block behaviour with a fake key, no real key needed.

4. Set a Monthly spend cap and restrict the key

In AI Studio, open the project's Spend tab and set a Monthly spend cap (Google's announcement). Google says caps have about a 10-minute delay and overages in that window are still billed (billing docs), so treat it as a ceiling, not a fix. Also restrict the key to the Generative Language API in Google Cloud's Credentials page. Referrer restrictions alone don't help: any script outside a browser can set that header.

Full guide with an FAQ: keyvaultedge.com/guides/ai-studio-app-export-gemini-key-leak

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.