This is the one Supabase mistake that is a genuine emergency. The service_role key bypasses Row Level Security entirely and has full admin access, so if it reached the browser or a public repo, anyone who found it can read, change, or delete everything in your database. Move quickly, in this order.
1. Rotate the key now
Do this first, before anything else. In your Supabase dashboard under Project Settings → API, generate a new JWT secret — this invalidates both the anon key and the service_role key and generates new ones. Update every environment variable that held the old value and redeploy. Every minute the exposed key is still valid is a minute someone can use it. Do not start by cleaning up the code; start by making the leaked value useless.
Scan your own app for issues like these
Paste your live URL. We check what your app serves publicly for exposed keys and misconfigurations. No account, no install.
2. Get it out of the client and the repo
The service_role key must never be in client code or committed to a repository. Remove it from any client component or browser-reachable file. If it was committed, deleting the file in a new commit is not enough — it stays in git history. Purge it with git-filter-repo:
# Install git-filter-repo first: pip install git-filter-repo or brew install git-filter-repo
# Option A: remove the .env file from all history
git filter-repo --path .env --invert-paths
# Option B: redact just the key value wherever it appears
printf 's/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.YOUR_ACTUAL_KEY/REDACTED/g' > replacements.txt
git filter-repo --replace-text replacements.txt
# After rewriting, force-push and tell collaborators to re-clone
git push origin --force --allThe new key belongs only in a server-side environment variable with noNEXT_PUBLIC_ prefix:
# .env.local (gitignored)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI...
# In server code only — never in a client component
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // no NEXT_PUBLIC_ prefix = server only
);3. Check for damage
Assume the key was found and used. In the Supabase dashboard, check your table data for unexpected inserts, updates, or deletes. Look at your project’s logs under Logs → API Logs and filter for your service_role key (it appears in the Authorization header as a Bearer token). If you see requests you do not recognize, treat this as a confirmed breach. If you hold sensitive user data, consider your notification obligations — GDPR, CCPA, and SOC 2 all have breach reporting requirements.
4. Fix the root cause: configure RLS
The service_role key usually ends up on the client because someone used it to skip Row Level Security. The real fix is to set RLS up so the client only ever needs the anon key. Enable RLS on every table with owner-scoped policies, and confirm an anonymous read returns nothing:
-- Enable RLS on every table
alter table public.your_table enable row level security;
-- Allow users to read only their own rows
create policy "read own rows"
on public.your_table for select
using ( auth.uid() = user_id );
-- Verify: an anon request should return nothing
-- Test with your public anon key (not service_role)
const anonClient = createClient(url, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY);
const { data } = await anonClient.from('your_table').select('*');
console.log(data); // should be [] for an unauthenticated requestFull policy examples in the Supabase RLS policy examples guide, and the complete launch checklist in the Supabase RLS checklist.
Frequently asked questions
How bad is an exposed service_role key?
As bad as it gets for a Supabase project. The service_role key bypasses Row Level Security completely and has full admin access, so anyone who has it can read, change, or delete any data in your database, regardless of your policies. Unlike the anon key, which is public by design and limited by RLS, the service_role key is meant to stay server-side and grants total control. Treat an exposure as a full database compromise.
Is rotating the key enough, or do I need to do more?
Rotating is the essential first step, but assume the exposed key was already used. If it sat in a public repo or a browser bundle, bots may have found it, so after rotating, check for signs of abuse: unexpected data changes, new or deleted rows, and unfamiliar activity in your logs. Rotation stops future access with the old key; it does not undo anything already done with it.
How did the service_role key end up in my client code?
Usually an AI tool or a copied snippet used it to make an operation work without configuring Row Level Security, since the service_role key sidesteps RLS and just works. Then that code ran in the browser or got committed. The fix is not only to remove the key but to set up RLS so you never need the service_role key on the client in the first place.
Which key should the client actually use?
Only the anon key, which is designed to be public and is limited by your RLS policies. The service_role key belongs exclusively in server code, edge functions, or route handlers, read from an environment variable. If a client operation seems to need the service_role key, that is a sign RLS is not set up correctly, and the answer is to configure RLS, not to expose the admin key.
How do I remove a secret from git history?
Use git-filter-repo (the modern replacement for filter-branch). Install it with pip or brew, then run: git filter-repo --path-glob '*.env' --invert-paths to remove all .env files from every commit. Or use git filter-repo --replace-text to redact just the key string. After rewriting history, force-push and invalidate any cached clones your collaborators have. GitHub offers a push protection and secret scanning service that warns before a secret reaches the remote.
What does 'service_role' mean in Supabase?
It is one of the two API keys Supabase generates for every project. The anon key is public-safe: it respects Row Level Security policies and is meant to be in your browser-side client. The service_role key (also called the secret key in newer Supabase UI) bypasses all RLS policies and acts as a superuser. It is intended only for trusted server code — migrations, admin scripts, and edge functions that need to operate across all users' data.
Can someone really find my key if it was in a private repo?
Yes, for several reasons. Private repos that become public expose the full git history, not just the current state. Collaborators' forks and clones copy the history to their machines. GitHub's own secret scanning may flag it. And if the repo was public even briefly, bots crawl GitHub continuously and index secrets within seconds of exposure. Rotation is the right response regardless of how long the key was visible.
Confirm it's really gone
After rotating and cleaning up, verify the service_role key is not still sitting in a bundle or your history somewhere. A scan reads what your deployed app serves and the repo the way an attacker's bot does, and flags a service_role key that reached the client. Run a free scan to confirm.