Secret Exposed to the Browser
A secret is being shipped to the browser, where anyone can read it.
Why it matters
Anything under NEXT_PUBLIC_, VITE_ or REACT_APP_ is copied into the JavaScript your users download, and so is anything a browser component reads from process.env. Someone opens DevTools, searches the bundle, and has your key. With an OpenAI or Stripe key that means running up your bill or refunding themselves. With a Supabase service role key it means reading and deleting every row in your database.
What it looks like
This is the shape of code that triggers the rule. AI tools produce it because it works, and nothing tells them it is unsafe.
const openai = new OpenAI({ apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY });The smallest fix
# Rename the variable so it is NOT public, and use it only on the server:
# .env: OPENAI_API_KEY=sk-... (no NEXT_PUBLIC_ / VITE_ prefix)
# Then call it from an API route or server action, never from a component:
export async function POST(req) {
const key = process.env.OPENAI_API_KEY; // server only
...
}The better fix
If you have a few more minutes, this is the approach that holds up as the app grows.
// Keep the browser away from the secret entirely: the browser calls your
// own endpoint, and your endpoint calls the third party with the key.
// Browser: await fetch('/api/summarize', { method: 'POST', body })
// Server: /api/summarize reads process.env.OPENAI_API_KEY
// If it is a Supabase key: the browser gets the anon key with RLS on;
// the service_role key stays on the server, always.Let your AI tool fix it
When the scanner finds this in your project, it fills in the file and line for you. This is the prompt it gives you to paste into Claude Code, Cursor or whatever you use.
How to check the fix worked
1. Build the app and search the output folder for the secret's value. It must not appear. 2. Open the deployed site, open DevTools > Sources, and search for the variable name. It must not appear. 3. Confirm the feature still works through the server endpoint.