Critical severity

How to fix an exposed Supabase service role key

A Supabase service role key is in your client-side JavaScript. This key bypasses Row Level Security — anyone who extracts it from your bundle has full read and write access to your entire database. The fix: rotate the key immediately in Supabase dashboard, remove every reference to it from client code, and only use it from server-side code (API routes, server components, serverless functions).

Why it matters

Supabase has two keys. The anon key is safe to expose — it works with RLS. The service role key is admin. The naming is confusing and AI tools sometimes use the service role key everywhere. Once it is in your client bundle, it is game over for your database.

How to check

  1. 01In your Supabase dashboard, go to Settings → API. Copy the service role key.
  2. 02View your deployed site source (Ctrl+U) and search for the key.
  3. 03Also check the Network tab — look for a `Authorization: Bearer ...` header that starts with `eyJ...` and contains `"role":"service_role"` when decoded.

Or let SafeToShip check it for you in 60 seconds:

How to fix it

Supabase dashboard

Rotate the key: Settings → API → Reset service_role key. This invalidates the leaked one.

Next.js

Move service-role Supabase calls into server code only. The service role key should be SUPABASE_SERVICE_ROLE_KEY (no NEXT_PUBLIC_ prefix) and only used in Route Handlers or Server Actions.

// lib/supabase-admin.ts — server-only
import { createClient } from '@supabase/supabase-js';

export const supabaseAdmin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,  // no NEXT_PUBLIC_
  { auth: { persistSession: false } }
);

// app/api/admin/route.ts
import { supabaseAdmin } from '@/lib/supabase-admin';
// Use supabaseAdmin here only.

Lovable

In Lovable, only the anon key should be in the generated frontend code. If Lovable is using the service role key client-side, tell it: 'Use only the Supabase anon key in the frontend. Move any service-role operations to a server-side function.'

AI prompt

Copy-paste into your AI tool

Paste this prompt into Cursor, Lovable, Bolt, v0, or Claude Code and it will walk through the fix for your specific codebase.

My Supabase service role key is in my client-side JavaScript. First, rotate the key in Supabase dashboard. Then search my codebase for every use of the service role key and determine which ones are legitimately server-side (API routes, server actions, backend functions) and which are wrongly in client code. For client usage, replace with the anon key + proper RLS policies. For server usage, make sure the key is only in a server-only file and the env var does not have a NEXT_PUBLIC_ prefix.

FAQ

Frequently asked questions

I'm not sure which key is which.
Decode the JWT at jwt.io. Look at the `role` claim. `service_role` is the admin key. `anon` is the safe-to-expose one.
What does the anon key look like in my code?
The anon key is a JWT starting with `eyJ...`. Its role claim is `anon`. It is safe to include in client code *if* you have RLS enabled on every table.

Scan your site for this and 50+ other issues

Free scan. Results in 60 seconds. No account required.