My Supabase service_role key is exposed

This is the worst key you own, and it is also the one most likely to end up committed โ€” because it's the one that makes your migration script work. Here's what to do, in order.

Do this first

  1. Confirm it's actually the service_role key and not the anon key. They look identical and only one is an emergency. Thirty seconds, below.
  2. Rotate it in the Supabase dashboard, under Settings โ†’ API.
  3. Then look at your data, not just your billing. This is the part people skip.

1. Which key is it? Decode it and see

Supabase gives you two keys that look the same โ€” long, three-part strings starting eyJ. One is public by design and one is catastrophic. Before you do anything else, find out which one you're holding.

They're JWTs, which means they aren't encrypted, just encoded. Paste the middle section (between the two dots) into any base64 decoder, or run:

echo 'PASTE_THE_MIDDLE_SECTION' | base64 -d

You'll get something like {"iss":"supabase","role":"anon",...} or {"role":"service_role",...}. That word is the whole answer:

RoleWhat it means
anon Not an emergency. This key is meant to be public โ€” it's in your page source and always has been. It only does what your Row Level Security policies allow. If this is the one that leaked, you have not lost anything. Go and check your RLS policies are actually written, then get on with your day.
service_role Emergency. Keep reading.

Newer Supabase projects use a different format โ€” sb_publishable_โ€ฆ and sb_secret_โ€ฆ โ€” where the name tells you directly. Same distinction, less decoding.

2. Why this one is different from other leaked keys

A leaked OpenAI key costs you money. A leaked service_role key costs you your data, and the difference matters for what you do next.

The service_role key bypasses Row Level Security entirely. Not "has broad permissions" โ€” bypasses. Every policy you carefully wrote, every auth.uid() = user_id check, stops applying. Anyone holding that key can read, edit and delete every row in every table in your project, from their own laptop, without touching your app.

That's why rotating isn't the whole job. With most leaked keys, the question is "what did it cost me". With this one, the question is "what did they see, and did they change anything" โ€” and the answer isn't in your billing page.

3. Rotating it โ€” and the thing that catches people

In the dashboard: Settings โ†’ API. Newer projects can revoke and reissue the secret key on its own, which is clean and uneventful.

Older projects, using the JWT-based keys, have a catch worth knowing before you click:

Regenerating the JWT secret invalidates both keys and signs every user out. The anon key changes too โ€” so any deployed frontend still holding the old one stops working until you redeploy with the new value โ€” and existing sessions become invalid, so all your logged-in users get logged out at once.

That's survivable, and it's much better than leaving the key live. But do it knowing it will happen, rather than discovering it from a support message ten minutes later. Have the new anon key ready to deploy before you rotate.

Deleting the key from the file is not rotation. It stays in your git history, and public repositories are crawled for credentials within minutes of a push โ€” frequently faster than you'll finish reading this page.

4. What to actually check afterwards

Because the key bypasses RLS rather than using it, ordinary application logs won't show the access โ€” it didn't go through your app. Look at the data itself:

If your project holds other people's personal data and you find evidence of access, that may be a reportable breach where you are. That's a question for someone who knows your jurisdiction, and it's a much easier question to answer while the dates are fresh.

5. Where it usually leaked from

Scanning a couple of hundred public AI-built apps, the service_role key was the most common serious finding by a distance โ€” and it was almost never in the application code. It was in the supporting cast:

Notice what those have in common: none of them is the app. If you're checking whether you've done this, grep the whole repository, not just src/.

6. Keeping it out next time

The service_role key belongs in exactly two places: a server-side environment variable, and an edge function's secrets. Nowhere else, ever.

Paste this into whatever built the app:

Find every place my Supabase service_role key appears in this repository, including markdown files, deployment notes, scripts and config files. Replace each with a read from an environment variable named SUPABASE_SERVICE_ROLE_KEY, move any client-side usage into a server-side route or edge function, and list every file you changed so I can confirm nothing was missed.

Check whether there's another one

vibecheck scans your project for exposed keys โ€” decoding Supabase JWTs to tell a harmless anon key from a service_role one โ€” and tells you in plain English, with a fix prompt to paste back into your AI coding tool.

Scan my project โ€” free