criticalSecrets and API keys

Supabase Service Role Key in Browser Code

The Supabase service_role key is reachable from the browser.

Why it matters

The service_role key ignores every row-level security policy you have. Anyone who finds it in your JavaScript bundle can read, change or delete every row in every table, as any user. It is the single most damaging thing to leak from a Supabase app, and it usually leaks because an AI tool used it to "fix" a permissions error in the browser.

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.

Service role key under a public prefix
const supabase = createClient(url, process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY);

The smallest fix

minimal patch
// Browser: only ever the anon key, with RLS enabled on your tables
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);

// Server only (API route, server action, edge function):
const admin = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);

The better fix

If you have a few more minutes, this is the approach that holds up as the app grows.

safe alternative
// If the browser needed the service key to make a query work, the real
// problem is a missing RLS policy. Write the policy for that table instead:
//   create policy "owners can read" on public.orders
//     for select to authenticated using (user_id = auth.uid());

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], the Supabase service_role key is exposed to the browser: [the flagged code]. Remove it from all client code and any NEXT_PUBLIC_/VITE_/REACT_APP_ variable. Use the anon key in the browser and move the query that needed the service key to a server route, or write the row-level security policy that makes the anon key sufficient. Then rotate the service_role key in the Supabase dashboard.

How to check the fix worked

1. Build the app and search the output folder for "service_role" and the
   key's value; both must be absent.
2. Run the feature that used to need the key; it must work through the
   server route or the new RLS policy.
3. Confirm the old key is rotated in Supabase > Settings > API.

Further reading