ShipSafeFull checklist (23 checks)

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

  1. List every NEXT_PUBLIC_, VITE_, EXPO_PUBLIC_ or REACT_APP_ variable. Each one must be safe to publish (publishable keys, the Supabase anon key, public URLs).
  2. Move secret keys (OpenAI, Anthropic, Stripe secret, Supabase service_role) to server-only variables without the prefix.
  3. Call the third-party API from an API route, server action or edge function, and have the browser call your route instead.
  4. 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

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

Tick it off in the full checklist

Get new checks by email

New checks and real leak post-mortems, about once a month. No spam.