Secrets and .env
No secret keys in the browser bundle
Find secret keys shipped to the browser via NEXT_PUBLIC_ or VITE_ variables and move them to server code.
Why it matters
Any variable your frontend can read is downloaded by every visitor and visible in DevTools. Next.js inlines NEXT_PUBLIC_ variables and Vite inlines VITE_ variables into the JavaScript at build time.
How to fix it
- List every
NEXT_PUBLIC_,VITE_,EXPO_PUBLIC_orREACT_APP_variable. Each one must be safe to publish (publishable keys, the Supabase anon key, public URLs). - Move secret keys (OpenAI, Anthropic, Stripe secret, Supabase service_role) to server-only variables without the prefix.
- Call the third-party API from an API route, server action or edge function, and have the browser call your route instead.
- Rotate any secret key that was ever shipped with a public prefix.
// app/api/chat/route.ts (runs on the server)
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // no NEXT_PUBLIC_
export async function POST(req: Request) {
const { message } = await req.json();
const r = await openai.responses.create({ model: "gpt-4.1-mini", input: message });
return Response.json({ text: r.output_text });
}How to verify
- Build the app and search the output:
grep -rE "sk-|sk_live_|service_role" .next/static dist. - Paste the built bundle into the ShipSafe key scan.
Questions
- Is the Supabase anon key safe in the browser?
- Yes, it is designed to be public, as long as RLS is enabled on every table.
- Is a Stripe publishable key (pk_) safe?
- Yes. Only secret (sk_) and restricted (rk_) keys must stay on the server.
Related checks
- Keep .env files out of git
- Rotate leaked keys, don't just delete them
- Rate limit expensive and auth endpoints
Tick it off in the full checklist