KeyDrift
Free scan
← Leak scenarios

Bolt.new apps and VITE_ secrets — same trap, faster

Everything in a Bolt preview runs in the browser, including any key you asked it to use. Scan the deployed result free before you trust it.

5 min read

The Bolt preview proved your call worked. It proved nothing about where the key lives — because in Bolt, everywhere is the browser.

Here is why WebContainer architecture makes client-held keys the default, and what changes on deploy.

What actually happened

You described an integration. Bolt scaffolded a Vite app inside its WebContainer, wired the key so the preview worked, and handed you a zip. Nothing errored on the way — that is what separates this failure from the ones your tooling catches.

  1. The WebContainer executes Node-ish code inside the browser sandbox — convenient for previews, irrelevant to production topology.
  2. Integration code referencing a VITE_ variable substitutes at build, exactly as in any Vite project.
  3. On deploy to a static host, the same chunks ship; the sandbox boundary that felt protective does not deploy.
  4. Result: preview behavior and production exposure are the same file set.

Every step above is individually reasonable and none of them prints a warning. The value crosses into the bundle during substitution, not execution, so nothing in your runtime ever sees the moment it happened.

What someone can do with it

Whatever credential made the integration work — payments, email, database — is now readable by every visitor:

  • Provider access defined by that key’s scope.
  • Abuse billed to your account.
  • Rotation forced under time pressure if discovered externally.

Rotate first

Do the rotation first. From the moment this value reached a public URL, treat it as public knowledge: browser caches, shared proxies and automated scrapers all hold copies you cannot recall. Rotate at the provider console first, then re-issue into server-side config only. Code changes come after, because a clean repository with a compromised key is still compromised.

A rotated key left in old deploys is still discoverable in CDN caches and archived copies. Rotation plus redeploy closes both halves; either alone leaves the door ajar.

Move the call somewhere the browser cannot read

The structural fix is always the same shape: the call moves to a context that holds the key without serving it, and the browser asks your server instead.

const res = await fetch(api, {
  headers: { Authorization: `Bearer ${import.meta.env.VITE_API_KEY}` },
});
// serverless function on your deploy target
export async function handler() {
  const res = await fetch(api, { headers: { Authorization: `Bearer ${process.env.API_KEY}` } });
  return { statusCode: 200, body: res.body };
}

Check whether yours is exposed

You can check manually right now: open the site, view source or open DevTools, and search the built JavaScript for VITE_. A hit means the string shipped; decode or prefix-check it before deciding how bad the news is.

The faster path is to let a machine do the fetching. KeyDrift downloads the same JavaScript a visitor gets — HTML, every referenced chunk including ones named only in the route manifest, and the server-streamed data frameworks inline into the document — and reports credentials with a masked prefix, a fingerprint, and the exact file they live in. Paste your deployed URL into the scanner; no account needed.

Keep it from coming back

It bears saying because it happens constantly: the fix holds until the next prompt that needs the query to return rows. Drift monitoring exists for precisely this — it diffs consecutive scans and pages you when a previously resolved finding reappears, naming the regression as a regression rather than repeating the first alert.

The bigger picture

It helps to name the economics honestly. Fixing this class of leak costs minutes when caught at deploy time and days when caught at invoice time, because by then the credential has been harvested, validated, resold or drained — often all four. Detection latency is the entire game, which is why the monitoring half of KeyDrift exists alongside the scanning half.

How KeyDrift reports this exact finding

When KeyDrift finds this on your deployment, the report shows a masked value (first 8 and last 4 characters only), a salted fingerprint for tracking, the exact chunk filename carrying it, and a severity with written rationale. Public-by-design neighbours — anon keys, publishable keys, Firebase web constants — appear as informational context rather than noise, because knowing what should be there is what makes the real findings credible.

Manual check, step by step

A five-minute version you can run anywhere: view-source on the landing page, copy every src= script URL, fetch each and search the results for VITE_. It misses manifest-only chunks and streamed payloads — which is precisely the gap between "I checked" and "it is clean" — but it catches the loud majority and builds the pattern-recognition that makes scanner output legible.

Close the loop with monitoring

Monitoring closes the loop that one-time verification leaves open. A scheduled scan refetches everything, diffs against history, and fires only on transitions: created, regressed, resolved. Regression alerts matter most here — they fire when a previously fixed finding returns, which in agent-era codebases is less a possibility than a schedule.

Common questions

The preview had no server either — is that the issue?

It is the tell. If the preview worked without a backend, no backend held the key; something client-side did.

Does exporting to GitHub change anything?

Not by itself. Repository location does not alter what the built bundle contains.


Run a free scan at keydrift.dev/scan — paste a URL or the bundle source itself, no account. Findings arrive masked, with the exact chunk they live in.


KeyDrift is an independent product and is not affiliated with, endorsed by, or sponsored by Bolt.new. The name is referenced descriptively.

Published by PostHat, KeyDrift’s content pipeline. Every factual claim is grounded in KeyDrift’s product documentation.