If your Supabase service_role key is in your JavaScript bundle, treat it as a full compromise and rotate it immediately, because that key bypasses Row Level Security and grants complete read and write access to your database to anyone who reads your bundle. A JavaScript bundle ships to the browser, so the key is public the moment the app is deployed, and minification does not hide it while source maps expose it in readable form. Rotate the key in the Supabase dashboard first to stop the bleeding, then remove it from your client code, rebuild without it, and move any operation that needed it to a backend. The key ended up in the bundle because your build tool inlined it into client code, which is exactly what must never happen with a secret.
Short answer
A service_role key in your JS bundle is fully exposed, so rotate it now and move it server-side. Per Supabase's API keys documentation, the service_role key bypasses Row Level Security and is meant only for trusted servers, so its presence in client code is a complete database compromise. Rotate it in the dashboard immediately, then remove it and rebuild. It reached the bundle because your build tool, such as webpack or Vite, inlined an environment variable referenced in client code, and source maps make it readable even after minification. Per Supabase's Row Level Security docs, only the public anon key belongs in the client; the service_role key never does.
Rotate it now
The first action, before anything else, is to rotate the service_role key, because for as long as the exposed key is valid, anyone who has your bundle has full access to your database. In the Supabase dashboard, roll or reset the service_role key so the exposed value stops working, then update your server-side code to use the new key. Do this immediately, since a shipped bundle is public and cannot be recalled, so rotation is what actually closes the exposure.
Rotation is only the first half, though. If you reissue the key and let it end up in the bundle again the same way, you have simply exposed a new secret, so rotation must be paired with removing the key from client code and rebuilding. After rotating, check your Supabase project and any logs for signs the key was already found and used, since a service_role key exposure means an attacker could have read or altered anything, and consider that the data may have been accessed while the key was live. Rotate first, then contain and fix.
Why the service_role key is a full compromise
The service_role key is not just another leaked credential; it is the most dangerous key Supabase issues, because it bypasses Row Level Security entirely. Where the public anon key is checked against your policies on every request and can only reach what you allow, the service_role key skips those checks and has full read and write access to all your data, by design, so trusted server code can perform privileged operations. When it is exposed, none of your Row Level Security policies protect you, because they do not apply to it.
That is why its presence in a client bundle is a complete compromise rather than a limited one. An attacker with the key can read every row in every table, modify anything, and delete your data, regardless of how carefully you wrote your policies, and they can do this directly against your database endpoint. This is categorically different from the anon key being visible, which is expected and safe under Row Level Security. So the exposure demands the emergency response of rotation, because the key hands over everything.
How it ended up in your JS bundle: webpack and env vars
Understanding how the key got into the bundle prevents it from happening again, and the cause is almost always your build tool inlining an environment variable. Bundlers like webpack and Vite replace references to certain environment variables in your client code with their literal values at build time, so if your client code reads the service_role key from an environment variable, the bundler writes the actual key string into the compiled JavaScript. Frameworks expose client variables through prefixes, such as the public prefixes in Next.js or Vite, and putting a secret behind one of those, or referencing any server variable in client code, inlines it.
So the key is in the bundle because it was referenced where client code could reach it and the bundler did what it is designed to do: inline the value for the browser. The fix at this level is to never reference the service_role key in client code at all, and never place it in a client-exposed environment variable. Server secrets belong in server-only environment variables that your bundler does not inline into client output. If a secret is read anywhere the client build can see it, expect it in the bundle.
Source maps expose it even when minified
A common false comfort is that minification hides the key, but it does not, and source maps make the exposure worse. Minification only renames and compresses code; string literals like your key survive intact, so a search of the minified bundle still finds it. And if you deploy source maps, the files that map minified code back to your original source, anyone can open the developer tools and read your code, including the key, as you wrote it, which removes even the minor friction of reading minified output.
So neither minification nor bundling protects the key, and published source maps actively expose it. When you check whether your key leaked, search the minified bundle for the key value and its prefix, and also check whether source maps are served, since they reveal the original code. As part of the fix, disable source map generation for production or ensure the map files are not published, though that is secondary to removing the key itself, since the key is extractable from the minified bundle regardless.
How to confirm and remove it
Confirming the exposure is a direct search, and removing it is a code change plus a rebuild. Download or open your deployed bundle and search it for the service_role key value and for the key prefix and terms like service_role, and check whether source maps are served. If you find the key, it is exposed, and you have already rotated it, so now remove every reference to it from your client code, since a reference is what caused the bundler to inline it.
After removing the references, rebuild your app and search the new bundle again to confirm the key is gone, including checking that no source map exposes it. Also search your repository and its history, since the key may sit in an old commit even after you delete it from the current code. The goal is a rebuilt, deployed bundle that contains no service_role key and no source map revealing one, with the key living only on your server. Verifying the rebuilt output, not just the source, is what confirms the removal.
Fix the architecture: never ship service_role
The lasting fix is architectural: the service_role key must live only on a server, and your client must never need it. Any operation that requires the service_role key, an administrative task, a privileged write that bypasses Row Level Security, runs on a backend you control, such as your own server or a Supabase Edge Function, which holds the key and performs the operation, returning only the result to the client. The browser calls your backend, not Supabase directly, for those operations, so the key stays server-side.
Your client uses only the public anon, or publishable, key, which is safe there because Row Level Security constrains it. So the boundary is clear: the anon key and Row Level Security handle everything the client does, and the service_role key handles privileged server work behind a backend. Designing this way means there is no code path where the client references the service_role key, so no bundler can inline it. Combined with keeping the key in server-only environment variables, this ensures the exposure cannot recur.
Safe versus unsafe keys
Sorting your Supabase keys makes the rule concrete. The table below compares them.
| Key | Belongs in the JS bundle? | Why |
|---|---|---|
| Anon or sb_publishable key | Yes, with Row Level Security | Public by design, constrained by policies |
| service_role or sb_secret key | Never | Bypasses Row Level Security, full access |
| JWT secret | Never | Signs tokens, server-side only |
| Database connection string | Never | Direct, unrestricted database access |
Read the table by privilege: only the public key, which is powerless without policies, belongs in client code, while everything that carries privilege on its own stays on a server.
Incident checklist
Working through these steps contains the exposure and prevents recurrence. The checklist below covers them.
| Step | Action | Done? |
|---|---|---|
| Rotate immediately | Roll the service_role key in the dashboard | [ ] |
| Check for abuse | Review the database and logs for misuse | [ ] |
| Remove the references | Delete every service_role reference from client code | [ ] |
| Rebuild and verify | Confirm the new bundle and source maps are clean | [ ] |
| Move to a backend | Run privileged operations server-side | [ ] |
| Secure the variable | Keep the key in a server-only environment variable | [ ] |
The step that actually closes the exposure is rotating the key, because a shipped bundle is public and only a new key makes the leaked one useless.
Scan your build for the key
Because a service_role key in the client defeats every policy you have, confirming what your build actually ships is worth doing after the fix, especially if an AI builder or a copied snippet wired it up.
A scanner like PTKD.com analyzes your build and reports issues such as leaked keys and secrets, insecure data storage, and over-broad permissions by severity, mapped to OWASP MASVS, so a service_role key that reached the bundle is flagged, while the anon key is expected. To be clear about the boundary: PTKD does not rotate your key or configure your bundler, which you do in Supabase and your build config. It finds the exposed secret in what you ship so you know what to rotate and remove.
What to take away
- A service_role key in your JS bundle is a full compromise, because it bypasses Row Level Security and grants complete database access to anyone who reads your bundle, so rotate it immediately.
- The key reached the bundle because a build tool like webpack or Vite inlined an environment variable your client code referenced, so never reference the service_role key in client code.
- Minification does not hide the key, since string literals survive, and published source maps expose your original code, so neither protects a secret.
- Rotate first, remove every client reference, rebuild and verify the bundle and source maps are clean, and search your repository history too.
- Move privileged operations behind a backend so the client never needs the service_role key, keep it in a server-only variable, and scan your build with a tool like PTKD.com.



