Home / Fixes / service_role in client

The service_role key in a client file is a public admin key

Vite and Next will happily ship whatever you put in src/. Prefixing it VITE_ does not hide it. It advertises it.

Symptoms

Why the builders do this

The first generated lib/supabase.ts needs a second argument. The dashboard shows two keys. The longer one “just works” for inserts the anon key rejects. Lovable, Bolt, and a lot of Cursor prompts copy it into the same file as the React client. Preview is green. The production bundle is an admin SDK with a UI.

A close cousin: window.env = { SUPABASE_SERVICE_ROLE: ... } so “runtime config” can change without a rebuild. Anything on window is readable in DevTools.

The actual fix

  1. Rotate now. Supabase dashboard → Project Settings → API → reset the service_role key. The old one is in git history and in every laptop that pulled the repo.
  2. Client uses anon only.
import { createClient } from '@supabase/supabase-js'

export const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_ANON_KEY
)

3. Service role stays on the server. Next.js app/api/**, a Vite-less Node route, a Supabase Edge Function. Env name: SUPABASE_SERVICE_ROLE_KEY with no VITE_ / NEXT_PUBLIC_ prefix.

4. Grep until it is gone.

grep -RInE 'service_role|SERVICE_ROLE' src app public components lib pages || true
grep -RInE 'VITE_.*SERVICE_ROLE|NEXT_PUBLIC_.*SERVICE_ROLE' . || true

5. Turn on RLS so the anon key is not an admin key with a nicer name. The RLS page is the other half of this fix.

If this app was ever on a public GitHub repo or a shared preview, assume strangers have the old key. Rotation is not optional.

How to know you are done

Build production locally (vite build / next build) and grep the output folder for the service_role JWT. Zero hits. Then, as a logged-in user, try to select someone else’s row — that should fail, which proves you are on the anon key with RLS.

$49 to run this against your tree

Zip or GitHub URL in. Ranked HTML/PDF in 24h. No call. First 10 audits are $49, credited if you continue to triage. Email contact@shipready.local.

Start an audit See a sample report