<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[KeyVault Edge Blog]]></title><description><![CDATA[KeyVault Edge Blog]]></description><link>https://keyvaultedge.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 13 Nov 2026 18:25:51 GMT</lastBuildDate><atom:link href="https://keyvaultedge.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Your AI Studio app export ships your Gemini key. Check dist/ for AIza]]></title><description><![CDATA[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…]]></description><link>https://keyvaultedge.hashnode.dev/your-ai-studio-app-export-ships-your-gemini-key-check-dist-for-aiza</link><guid isPermaLink="true">https://keyvaultedge.hashnode.dev/your-ai-studio-app-export-ships-your-gemini-key-check-dist-for-aiza</guid><category><![CDATA[gemini]]></category><category><![CDATA[Security]]></category><category><![CDATA[vite]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Keyvault Edge]]></dc:creator><pubDate>Wed, 11 Nov 2026 10:19:06 GMT</pubDate><content:encoded><![CDATA[<p>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.</p>
<p>The catch: depending on the template, the exported <code>vite.config.ts</code> can copy your <code>GEMINI_API_KEY</code> into the JavaScript that every visitor downloads. People on the Google AI Developers Forum have reported <a href="https://discuss.ai.google.dev/t/ai-studio-build-apps-export-silently-inlines-os-level-gemini-api-key-into-the-client-bundle-loadenv-with-empty-prefix-usd-4-010-in-unauthorized-charges/188776">four-figure bills from exactly this</a>.</p>
<h2>Check in 30 seconds</h2>
<pre><code class="language-bash">npm run build
grep -r "AIza" dist/
</code></pre>
<p>Any match means the key is public. You can also open your live site, search the JavaScript files in DevTools (Sources tab) for <code>AIza</code>, or watch the Network tab for calls to <code>generativelanguage.googleapis.com</code> that carry your key.</p>
<h2>Why it happens without a <code>VITE_</code> prefix</h2>
<p>Vite only exposes <code>VITE_</code> variables to client code by default (<a href="https://vite.dev/guide/env-and-mode">Vite env docs</a>). A <code>define</code> block bypasses that: Vite replaces the expression with a literal value at build time (<a href="https://vite.dev/config/shared-options#define">define option</a>). Look for this pattern:</p>
<pre><code class="language-ts">// vite.config.ts
import { defineConfig, loadEnv } from "vite";

export default defineConfig(({ mode }) =&gt; {
  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),
    },
  };
});
</code></pre>
<h2>Fix it, in this order</h2>
<h3>1. Rotate the key now</h3>
<p>If <code>grep</code> 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 <a href="https://keyvaultedge.com/leaked-api-key?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=ai-studio-leak">checklist for a leaked key, including what evidence to keep for a billing dispute</a>.)</p>
<h3>2. Remove the <code>define</code> entries</h3>
<p>Delete <code>process.env.GEMINI_API_KEY</code> (and <code>process.env.API_KEY</code>, if present) from <code>define</code>, remove the client code that reads them, rebuild, and run the <code>grep</code> again. It must return nothing.</p>
<h3>3a. Call Gemini from a server function</h3>
<p>Keep the key in a server-only variable (no <code>VITE_</code> prefix) and have the browser call your function. A Vercel function in a Vite project:</p>
<pre><code class="language-ts">// 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" },
  });
}
</code></pre>
<p>Netlify functions and Cloud Run handlers can use the same <code>Request</code>/<code>Response</code> objects. Add your own rate limiting or auth: anyone can call this endpoint.</p>
<h3>3b. Or skip the backend with a scoped token</h3>
<p>Disclosure: I build <a href="https://keyvaultedge.com/?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=ai-studio-leak">KeyVault Edge</a>, so weigh this accordingly.</p>
<p>If you don't want to run a server, KeyVault Edge holds the real <code>AIza</code> key at the edge and gives your app a <code>kvt_</code> token locked to your domain, to the <code>generateContent</code> endpoint, to a requests-per-minute limit and to a monthly cost cap:</p>
<pre><code class="language-ts">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 }] }] }),
  }
);
</code></pre>
<p>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 <code>Origin</code> header, up to those limits. There's a <a href="https://keyvaultedge.com/login?mode=signup&amp;next=/keys/new&amp;utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=ai-studio-leak">free sandbox</a> that shows the allow/block behaviour with a fake key, no real key needed.</p>
<h3>4. Set a Monthly spend cap and restrict the key</h3>
<p>In AI Studio, open the project's <strong>Spend</strong> tab and set a <strong>Monthly spend cap</strong> (<a href="https://blog.google/innovation-and-ai/technology/developers-tools/more-control-over-gemini-api-costs/">Google's announcement</a>). Google says caps have about a 10-minute delay and overages in that window are still billed (<a href="https://ai.google.dev/gemini-api/docs/billing">billing docs</a>), 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.</p>
<hr />
<p>Full guide with an FAQ: <a href="https://keyvaultedge.com/guides/ai-studio-app-export-gemini-key-leak?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=ai-studio-leak">keyvaultedge.com/guides/ai-studio-app-export-gemini-key-leak</a></p>
<p><em>Originally published at <a href="https://keyvaultedge.com/guides/ai-studio-app-export-gemini-key-leak?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=ai-studio-leak">keyvaultedge.com</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[The first 10 minutes after you leak an API key]]></title><description><![CDATA[Leaked an API key? Revoke, replace, check usage, cap spend, purge git history, then scan for the rest. A minute-by-minute checklist.]]></description><link>https://keyvaultedge.hashnode.dev/the-first-10-minutes-after-you-leak-an-api-key</link><guid isPermaLink="true">https://keyvaultedge.hashnode.dev/the-first-10-minutes-after-you-leak-an-api-key</guid><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Git]]></category><category><![CDATA[api]]></category><dc:creator><![CDATA[Keyvault Edge]]></dc:creator><pubDate>Mon, 05 Oct 2026 11:21:39 GMT</pubDate><content:encoded><![CDATA[<p>You just found your API key somewhere it shouldn't be: a public repo, a screenshot in a support ticket, a frontend bundle, a CI log. Here's what to do, in order, and why the order matters.</p>
<h2>Minute 0–2: revoke it</h2>
<p>Go to the provider dashboard and revoke or delete the key <strong>now</strong>.</p>
<p>Don't first try to work out whether it was used. Leaked keys in public places get found by automated scanners, and you can't prove a negative from your side. Assume it was seen.</p>
<p>Revoking first also means everything after this point is cleanup, not damage control.</p>
<h2>Minute 2–5: replace it</h2>
<p>Create a new key and roll it out before anything else breaks:</p>
<ul>
<li>Update the secret in your hosting provider (Vercel, Fly, AWS, wherever it lives)</li>
<li>Update CI secrets</li>
<li>Redeploy, and confirm the app works with the new key</li>
</ul>
<p>If more than one service shared the old key, this is the moment you find out. That's worth fixing later too: one key per service makes the next rotation smaller.</p>
<h2>Minute 5–7: check whether it was used</h2>
<p>Open the provider's usage or billing page and look at the window between when the key leaked and when you revoked it. Signs of abuse:</p>
<ul>
<li>Models, endpoints or products you don't use</li>
<li>Requests from regions you don't operate in</li>
<li>A usage spike that doesn't match your traffic</li>
</ul>
<p>For AWS keys, check CloudTrail for API calls the key made; attackers often create new IAM users or access keys so they keep access after you revoke the first one.</p>
<h2>Minute 7–8: cap the blast radius</h2>
<p>If the provider supports it, set a <strong>hard spending limit</strong> and a usage alert on the account. It doesn't help with this leak, but it bounds the next one.</p>
<h2>Minute 8–10: remove it from where it leaked</h2>
<p>If it was committed to git, deleting the file isn't enough: it's still in history.</p>
<pre><code class="language-bash"># Rewrite history to remove the file (or use --replace-text for a string)
git filter-repo --path path/to/file-with-key --invert-paths
git push --force --all
git push --force --tags
</code></pre>
<p>Then:</p>
<ul>
<li>Tell collaborators to re-clone; their copies still contain the old history</li>
<li>Assume forks keep it forever. You can't rewrite other people's forks, which is exactly why revoking came first</li>
<li>If it's on GitHub, ask GitHub support to purge cached views of the old commits</li>
</ul>
<p>If it leaked somewhere else (an error tracker, logs, a ticket), delete or redact it there too.</p>
<h2>After the 10 minutes: find the others</h2>
<p>A leaked key is rarely the only one. Scan everything:</p>
<pre><code class="language-bash"># Full history scan
gitleaks detect --source . --verbose

# Verified findings only (keys that still work)
trufflehog git file://. --only-verified
</code></pre>
<p>And check the places scanners miss: CI secrets, other repos, teammates' <code>.env</code> files, Docker image layers.</p>
<h2>Then: write down how it got out</h2>
<p>Was it a <code>.env</code> committed by accident? A key in a <code>NEXT_PUBLIC_</code> variable? A debug step printing the environment? The fix that matters is the process change (a pre-commit hook, push protection, moving the call server-side), not the new key.</p>
<h2>Making the next leak cheaper</h2>
<p>Rotation fixes the leak you know about. For the ones you don't, the goal is that a leaked string is worth as little as possible: scoped keys, spending limits, and short-lived or origin-bound tokens.</p>
<p>That last part is what I'm building with <a href="https://keyvaultedge.com">KeyVault Edge</a>: your code ships a token, and an edge proxy swaps it for the real key only on requests from origins you've allowed. It's one layer alongside rotation and scanning, not a replacement for them.</p>
]]></content:encoded></item></channel></rss>