highSecrets and API keys

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.

Secret under NEXT_PUBLIC prefix
const openai = new OpenAI({ apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY });

The smallest fix

minimal patch
# 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.

safe alternative
// 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.

In [the file] at line [the line number] a secret is exposed to the browser: [the flagged code]. If the variable has a public prefix (NEXT_PUBLIC_, VITE_, REACT_APP_), rename it without the prefix and move every use of it into server code (an API route, server action or backend). If this is a browser component reading process.env, create a server endpoint that uses the secret and call that endpoint from the component instead. Then rotate the key, because it may already be in a deployed bundle.

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.

Further reading