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.
const supabase = createClient(url, process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY);The smallest fix
// 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.
// 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.
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.