Skip to content
KeyDrift
Scan for free
criticalBolt.new·OpenAI API key

Your Bolt.new app is serving its openai api key to every visitor

Billable API access with no test-mode equivalent. A published key is spendable by anyone who reads the bundle, and the spend is billed to your organisation.

Why Bolt does this

Bolt scaffolds Vite projects inside a WebContainer, where everything runs in the browser by definition. Code that works in the preview keeps the key client-side when it is deployed, because nothing in the generated project ever moved it to a server.

Confirm it first

Before rotating anything, check whether the key is actually being served. Paste your deployed URL — KeyDrift downloads the same JavaScript a visitor gets and tells you what is in it.

No account. Read-only — the scanner only ever issues GET and HEAD requests, and never stores a key: findings carry a masked prefix and a fingerprint.

Rotate the key

Do this before changing any code. The key has been served to browsers, cached by CDNs and very likely scraped already — removing it from the source does not un-publish it.

  1. 1Revoke the key at platform.openai.com/api-keys.
  2. 2Check usage for the billing period — a published key is spendable by anyone who read it, and the charge tends to arrive before any alert does.
  3. 3Put the API call behind a server route with your own rate limiting, so a leaked route costs less than a leaked key.
Open the revocation page

Move the call to a server

The replacement key must not follow the old one into the bundle, which means the code that uses it cannot live in the browser. Any variable named VITE_… is inlined at build time by design — the prefix is the mechanism, not a mistake.

Before — shipped to the browser

// src/components/Chat.tsx
// Vite substitutes the literal value here at build time.
const key = import.meta.env.VITE_OPENAI_API_KEY;
const result = await new OpenAI({ apiKey: key }).chat.completions.create(body);

After — stays on a serverless function on your deploy target

// supabase/functions/chat/index.ts  — runs on a serverless function on your deploy target
Deno.serve(async (request) => {
  const key = Deno.env.get('OPENAI_API_KEY')!; // never sent to the browser
  const result = await new OpenAI({ apiKey: key }).chat.completions.create(body);
  return Response.json(result);
});

// src/components/Chat.tsx
const result = await fetch('/functions/v1/chat', { method: 'POST' }).then((r) => r.json());

How KeyDrift detects it

Matches the modern `sk-proj-`, `sk-admin-` and `sk-svcacct-` prefixes over the full base64url alphabet, so a key containing `-` or `_` is captured whole rather than truncated at the first one.

It will happen again

Bolt scaffolds Vite projects inside a WebContainer, where everything runs in the browser by definition. That has not changed because you fixed this one file — the next feature request produces the same shape of code. Continuous monitoring re-scans every deploy and tells you the moment a key comes back.