KeyDrift
Free scan
criticalCursor·OpenAI API key

Cursor put a openai api key in a client component — here's the fix

Billable API access. Scraper bots drain exposed keys within minutes of publication.

Why Cursor does this

Cursor edits the file you have open. Ask it to "call the OpenAI API" from a component and it writes exactly that — a client component holding a key — because the request named a file, not an architecture. The agent has no way to know the module is bundled for the browser.

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 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 — exposed keys are usually found by scrapers within minutes, and the charge lands before the 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.

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 route handler or server action

// supabase/functions/chat/index.ts  — runs on a route handler or server action
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

Cursor edits the file you have open. 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.