Cenk Kurtoğlu©26

Supabase security · 5 minute read

Leaked Supabase service_role key: the 30-minute rotation runbook

A service_role key in a public repository is not an information leak — it is a complete bypass of every RLS policy you have. Anyone holding it reads, writes and deletes across your whole database with the PostgREST API and no login. The fix has one correct order, and most people do it backwards: they purge the git history first and rotate last. Rotate first.

Why the order is: rotate → verify dead → purge → prevent → prove

Step 1 — rotate in the dashboard (2 minutes)

Project Settings → API → service_role secret → roll it. Do this before touching git. Note the timestamp: that is the start of your exposure window for the incident writeup, and every deployment that used the old key needs the new one from your secret store.

Step 2 — verify the old key is dead (1 minute)

curl -s -o /dev/null -w '%{http_code}' \
  "$SUPABASE_URL/rest/v1/?apikey=$OLD_SERVICE_ROLE_KEY" \
  -H "Authorization: Bearer $OLD_SERVICE_ROLE_KEY"

# healthy rotation: 401
# still live: 200 - keep the old one revoked and re-check after propagation

A 200 immediately after rotation can be propagation delay — re-check after a minute. A persistent 200 means the roll did not take; do not continue to the history steps until this reads 401.

Step 3 — purge it from git history (10 minutes)

The key string is in history even after you delete the file. Two tools, both destructive — they rewrite every commit SHA, so coordinate with your team:

# git-filter-repo (recommended; install: pip install git-filter-repo)
echo "$OLD_SERVICE_ROLE_KEY" > /tmp/secret.txt
git filter-repo --replace-text /tmp/secret.txt
git push --force --all && git push --force --tags

# or BFG: bfg --replace-text /tmp/secret.txt

Then delete and re-clone: filter-repo warns that the old objects stay in your local clone until you re-clone. Forks and PR refs keep their own copies — if the repository was popular, treat the key as permanently public even after purging, which is exactly why step 1 came first.

Step 4 — make it not happen again (5 minutes)

Step 5 — evidence for the incident (10 minutes)

Two answers protect you later: what the key could reach, and whether it did. Reconstruct the first from the schema:

-- with the NEW key: what could the old one touch?
select table_name from information_schema.tables
where table_schema in ('public','storage');

select count(*) from auth.users;   -- reachable via /auth/v1/admin with the key

For the second, the honest answer is usually "we cannot know" from the database alone — REST access through the Data API is not logged per key unless you have log retention configured. State that plainly, and check your project logs for the window. If any user-scoped table was reachable, the incident template in the audit kit covers what to tell affected users.

The check that matters after the rotation

Rotating the key restores the posture your policies already promised — so the final proof is that the policies actually hold without it. Run the anon-key probe from the first-15-minutes audit guide: if the public anon key can read user-scoped tables, the leak was never the worst of it — your RLS was already open, and the service_role exposure just removed the need for it. That combination — a live key plus open policies — is what a fixed-price audit from $99 is for.

The runbook as files, plus the regression SQL

Supabase RLS Audit Kit · $29 one-time

The rotation checklist, history-purge commands, gitleaks pre-commit setup and RLS regression SQL from this article, plus the seven commented audits and the role-simulation harness that prove isolation after the rotation.

Get the kit - $29 ↗Review the files, scope and license →

Related reading: 7 ways your Supabase app still leaks data with RLS enabled, what your AI security reviewer structurally cannot see and why your RLS tests pass and your data still leaks.

Template bundle

Review all 20 frontend starters in one bundle

20 editable frontend starters with live demos are $79 — one payment, no subscription.

View the Bundle — $79↗