Skip to main content

Credential finding · Software and cloud

Supabase Service Role Key Exposed: Rotate It, Then Check What It Touched

Your Supabase service role or secret key ended up in client code, a repo, or a log. How to rotate it, retire the old key, and check what it was used for.

How it shows up

Supabase Secret Keysupabase_secret_keyNEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEYVITE_SUPABASE_SERVICE_ROLE_KEY

A Supabase secret key, or the legacy service role key it replaces, has been found somewhere outside your server: a JavaScript bundle, a public or shared repository, an environment variable with a public prefix such as NEXT_PUBLIC_ or VITE_, a log, or a screenshot. That key bypasses row level security completely. Anyone who copied it can read, change, and delete the data in every table your API exposes, your stored files, and your user accounts, regardless of the policies you wrote. Legacy keys are long tokens that start with eyJ; newer secret keys start with sb_secret_.

Why it matters

Row level security is the lock on every table, and this key is the one Supabase gives your server so it can work without the lock. Once it is outside the server, the policies you wrote stop protecting anything from whoever holds it. A database exposed this way gives no warning: the requests look like your own backend.

The leak usually happens by accident. A key copied into a frontend environment variable gets compiled into the JavaScript every visitor downloads. Code generated quickly, by people or by coding assistants, reaches for the key that makes an error go away, and the secret key always works. A debugging session logs a request header. Each of these keeps the app running perfectly, which is why the key can sit exposed for weeks.

Supabase has narrowed the damage for its newer keys. A secret key returns HTTP 401 when it comes from a browser, and GitHub reports one pushed to a public repository to Supabase for revocation. Neither protection stops someone who copied the key from a bundle or a log and uses it from a script, and the legacy service role key has neither.

The fix

Find how it leaked, then rotate. Remove the key from the client code, repository, or log it reached, and fix the build setting that put it there, or the replacement will leak the same way. If the logs show someone actively using the key, retire it first, accept the brief breakage, and fix the leak right after.

Create a new secret key for each backend component. In Settings, under API Keys, create a secret key per server, function, or job that needs one. A separate key per component means the next leak costs one rotation instead of all of them.

Store the new key where only the server can read it. Put it in a managed secrets store, such as AWS Systems Manager Parameter Store or Secrets Manager, encrypted under a key your company controls. The server fetches it at startup through a role scoped to that one secret, so the key never sits in the build, the repository, or an environment file a frontend can read. Write it there straight from the dashboard or a pipeline, so nobody pastes it into chat or a ticket on the way. Then turn on audit logging for reads of that secret (CloudTrail on AWS) and alert when anything other than the server's own role reads it.

Replace it everywhere, then retire the old key. Update every server, Edge Function, database webhook, and scheduled job that used it. Delete a compromised secret key; deactivate the legacy keys if the leak was the service role key, and move browser and mobile clients to a publishable key first, since deactivation retires the anon key too. Never turn a retired key back on to rescue a client you missed; give that client a new key instead.

For a legacy service role leak, revoke the JWT secret. The legacy keys are tokens signed by your project's JWT secret, so anything that still trusts that secret can still accept the leaked key. Supabase's path is to migrate to JWT signing keys and revoke the legacy secret. Revoking signs out users whose sessions were issued under the legacy secret; during an active incident that is the right trade. Check first whether any of your code verifies tokens against the legacy secret directly, since that code breaks at revocation.

Check what the key touched, and export the logs now. Log retention depends on your Supabase plan, so pull the API gateway logs for the window between exposure and retirement before they age out. Look for requests from addresses that aren't yours and calls to tables, storage, or user administration your backend doesn't make, and compare your data against a recent backup for changes nobody on your team made. The logs may not show which key made a request, and database reads appear only if statement logging was on, so an empty result does not prove the key went unused. If the exposure is older than your retention, record that use cannot be ruled out. Then confirm the retired key is rejected, and keep the timeline and the review as the evidence that the finding is closed.

In practice: catching the key before it ships

Rotation closes this leak; the next one comes from the same build setting or the same shortcut in new code. Secret scanning on commits catches keys in source. It misses the key that only appears once the app is built, inside the JavaScript a visitor downloads.

Our Release Verification & Readiness gate scans what each release actually serves (API responses, server-rendered pages, and every client bundle) for credentials, server secrets, and other people's data, and fails the release when it finds one. The same release is replayed as the wrong user and the wrong customer, so the release that introduces a leaked key or a policy that never held fails the gate. For keys in code and configuration more broadly, see Hardcoded Credentials (CWE-798); for tables reachable without the key at all, see RLS Disabled in Public.

When you can't fix it

An exposed key with full access has no safe holding pattern. What can wait is the cleanup around it, when rotation would break a production integration you can't redeploy today. Your named owner chooses how fast.

  • Remediate now: rotate and retire the key, and record the time of each step.
  • Mitigate for a few hours at most, when an integration needs time to move. Remove the key from every place it is exposed, watch the logs for unfamiliar use, schedule the rotation inside the same day, and move the integration to its own new key.
  • Accept does not fit here. A risk-acceptance record suits a system that can't change; a credential can always be replaced. If the key was exposed and nobody can say whether it was used, record that as an open incident with an owner.

Supabase is a trademark of Supabase Inc. FEDLIN is not affiliated with or endorsed by Supabase.

Where it lands in the Risk Register

FindingTreatmentOwnerEvidence
Supabase secret or service role key exposed outside the server, on a named projectRemediate by rotation. Acceptance does not fit a live credential with full access.Named by youTime the key was exposed and retired, the new key per component and where it is stored, the retired key confirmed rejected, and a log review of the exposure window, or a record that the logs had expired. Maps to NIST 800-53 IA-5(7), CWE-798 and CWE-532, OWASP ASVS 5.0 13.3.1, and SOC 2 CC6.1 and CC7.4.

Common questions

Is the Supabase service role key safe to expose?

No. The service role key, and the secret keys that replace it, bypass row level security entirely, so anyone holding one can read, change, and delete the data in every table your API exposes, your stored files, and your user accounts. It belongs on the server only. The key that is safe in a browser is the publishable key, formerly called the anon key, and only when row level security is on for every exposed table.

What is the difference between the Supabase service role key and the anon key?

The anon key, now the publishable key, identifies your app and is meant to be public; row level security decides what it can reach. The service role key, now a secret key, carries the BYPASSRLS attribute, so no policy applies to it. One is a name tag, the other is a master key.

Where do I find the Supabase service role key?

In your project dashboard under Settings, then API Keys. Newer projects show publishable and secret keys there; the legacy anon and service_role keys sit alongside them until you deactivate them. Treat any secret or service role key you copy from that page as you would a database password.

Is the Supabase anon key exposed a problem?

No, by design. The anon key, now the publishable key, ships in every browser that loads your app. What matters is row level security on each table the API exposes; see our page on RLS Disabled in Public. A secret or service role key in the same place is the emergency.

Where should I store the Supabase service role key?

On the server side only, in a secrets store such as AWS Systems Manager Parameter Store, AWS Secrets Manager, or your host's encrypted environment settings, never in a variable with a public prefix like NEXT_PUBLIC_ or VITE_. Give each backend component its own key, scope access to the one role that needs it, and log every read.

Is the Supabase service role key deprecated?

Supabase is deprecating the legacy anon and service_role keys by the end of 2026 in favor of publishable keys (sb_publishable_) and secret keys (sb_secret_). Secret keys can be created per backend component and deleted one at a time, and they return HTTP 401 when called from a browser. A leaked one still works from any other tool, so the rotation steps on this page still apply. If an AI app builder or a template set up your project, search the published JavaScript for sb_secret_ and for a long eyJ token labeled service_role.

Does Supabase revoke leaked keys automatically?

Supabase is a GitHub secret scanning partner: GitHub reports secret keys (sb_secret_) found in public repositories to Supabase for revocation, and can block them at push time. Legacy service_role keys are not on that list. That covers one leak path. It does not cover keys shipped in a JavaScript bundle, pasted into a log, or sitting in a private repository, and revocation does not undo anything done with the key while it was exposed.

Do I have to tell my customers?

It depends on what was reachable, whether anyone used the key, and what your contracts and the laws that apply to you require. Establish the facts first: when the key was exposed, when it was retired, and what the logs show in between. Then bring those facts to counsel. A customer's security questionnaire will usually ask how incidents are reported, so the timeline you build here is evidence either way.

Want every release checked for keys in the bundle before it ships, with evidence for your SOC 2 review?