RowShield
Guides

Rotating a service_role key that got out: rotate first

A service_role key found in a client bundle, a git history, or a screenshot is not a warning sign — it is a standing invitation. The key bypasses every Row Level Security policy in the project, so until it is invalidated, none of your policies exist for whoever holds it. Forensics can wait an hour. Invalidation cannot.

This page gives the rotation sequence in the order experience says it works: rotate before you understand, let the breakage draw the map, then relocate every caller so the next leak cannot recur in the same place.

Rules that check this

Rotate first, investigate second

The instinct after discovering exposure is to trace how it happened before changing anything, and the instinct is wrong here for one reason: tracing takes hours or days, during which the credential retains full authority. Rotation takes effect immediately and costs you a broken integration you were going to fix anyway. Open the dashboard, generate a replacement under Settings → API, update your server environments, and let the old key die.

Expect the breakage and use it. Everything depending on the dead key fails loudly and immediately — deploys error, cron jobs halt, that admin endpoint someone wired up last quarter stops answering — and the failure list is the authoritative inventory of everywhere the key lived. Security work rarely receives a complete asset register as a side effect; take it.

Relocate every caller behind your server

Each entry on the breakage list gets the same treatment: whatever operation needed elevated privileges moves into a route handler, server function or edge function where the replacement key lives in server-side environment variables, and clients call your endpoint instead of the database directly. An operation that genuinely requires bypassing RLS is, by that fact, an operation browsers have no business performing — the architecture follows from the credential's nature, not from preference.

Resist recreating the leak in the process. Environment variables prefixed for public bundles, client-side conditionals reading the wrong variable, and AI-generated code cheerfully wiring the convenient key into the nearest component are the three recurrence patterns; grep the rebuilt surfaces for the key's prefix before declaring victory.

grep -rn --include="*.js" --include="*.ts" \
  -e "sb_secret_" -e "eyJhbGciOi" ./dist ./build 2>/dev/null

Find where it leaked, then shrink the next blast radius

With exposure contained, the origin question becomes answerable and matters for prevention. Client bundles remain the dominant source — AI builders reaching for the powerful key because the prompt said admin — followed by committed environment files, pasted screenshots, logs echoing headers, and public issue threads. Git deletion removes the tip of the iceberg only; rotation already neutralised history, so document rather than rewrite.

Prevention is structural, not vigiliance: privileged operations live behind server routes as a matter of architecture, CI greps bundles for credential shapes before deploy, and continuous monitoring watches the deployed artifact rather than trusting the source. These overlap deliberately — any single layer fails eventually, and the combination makes recurrence unlikely rather than impossible.

How RowShield catches the next one

The probe fetches your deployed pages, follows same-origin script tags and module preloads, and scans the bundles for Supabase credential shapes — legacy JWTs classified by decoding their role claim, sb_secret_ and sb_publishable_ keys, sbp_ management tokens. A privileged key in browser-reachable code is a critical finding carrying rotate-first guidance; the stored record contains a fingerprint, six characters and a length, never the key itself. Scans run on schedule and alert on transitions, so a regression after next month's refactor arrives as an alert rather than as an incident. Requests stay GET, HEAD and OPTIONS; private address ranges are refused before any connection. RowShield is an independent product, not affiliated with or endorsed by Supabase. Scan your deployed site free at rowshield.dev/audit — a URL is the whole input.

Frequently asked

Will rotating the key cause downtime?
For everything legitimately using the old key, yes, starting the moment you rotate — that is the mechanism, since failures enumerate the call sites for you. Stage the replacement into server environments first, rotate, then redeploy the affected services, and the outage compresses to minutes.
Nobody visited the exposed site yet. Still rotate?
Yes. You cannot know who holds a public credential — scrapers archive constantly, and absence of observed damage is not evidence of absence of holders. Rotation is cheap and certain; betting on having gone unnoticed is neither.
Does RowShield store the leaked key it finds?
No. Findings persist a fingerprint — the first six characters plus a length — sufficient to recognise the credential later without possessing it. Rendering a live bypass-everything key inside a dashboard would compound the very leak it reports.

Check your project in about ten seconds

Paste a URL. No signup, no writes, nothing stored.

Run the free audit
rotate exposed supabase service roleservice_role key leakedsupabase rotate api keys