AI-coded apps

    Supabase Service Role Key Exposed in JS Bundle

    A minified JavaScript bundle with a Supabase service_role key inlined by webpack and revealed by a source map in the browser developer tools.

    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.

    KeyBelongs in the JS bundle?Why
    Anon or sb_publishable keyYes, with Row Level SecurityPublic by design, constrained by policies
    service_role or sb_secret keyNeverBypasses Row Level Security, full access
    JWT secretNeverSigns tokens, server-side only
    Database connection stringNeverDirect, 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.

    StepActionDone?
    Rotate immediatelyRoll the service_role key in the dashboard[ ]
    Check for abuseReview the database and logs for misuse[ ]
    Remove the referencesDelete every service_role reference from client code[ ]
    Rebuild and verifyConfirm the new bundle and source maps are clean[ ]
    Move to a backendRun privileged operations server-side[ ]
    Secure the variableKeep 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.
    • #supabase
    • #service role key
    • #js bundle
    • #source maps
    • #api key exposure

    Frequently asked questions

    How bad is a service_role key in my JS bundle?
    It is a full database compromise. The service_role key bypasses Row Level Security entirely and has full read and write access to all your data, so anyone who reads your bundle, which ships to the browser and is public once deployed, can read, modify, or delete anything directly against your database, regardless of your policies. This is categorically worse than the anon key being visible, which is expected and safe under Row Level Security. Rotate the service_role key immediately.
    How do I rotate the exposed key?
    In the Supabase dashboard, roll or reset the service_role key so the exposed value stops working, then update your server-side code to the new key. Do this immediately, before anything else, since a shipped bundle is public and cannot be recalled, so rotation is what closes the exposure. Then check your database and logs for misuse, since the key means an attacker could have accessed anything, and pair rotation with removing the key from client code.
    How did the key end up in my bundle via webpack?
    Build tools 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 into the compiled JavaScript. Frameworks expose client variables through public prefixes, like those in Next.js or Vite, so putting a secret behind one, or referencing any server variable in client code, inlines it. Never reference the service_role key in client code.
    Do source maps or minification hide the key?
    No. Minification only renames and compresses code; string literals like your key survive intact, so a search of the minified bundle still finds it. Published source maps make it worse by mapping the minified code back to your original source, so anyone can read your code, including the key, in the developer tools. Search the minified bundle for the key and check whether source maps are served, and disable production source maps, though removing the key itself is primary.
    What is the permanent fix?
    Keep the service_role key only on a server so the client never needs it. Run any operation requiring it behind a backend or Supabase Edge Function that holds the key and returns only the result, so the browser calls your backend rather than Supabase directly for privileged work. The client uses only the public anon key, safe under Row Level Security. With no client code path referencing the service_role key, no bundler can inline it, and keeping it in a server-only variable prevents recurrence.
    How do I confirm the key is gone after fixing it?
    Rebuild and search the new bundle for the key value and prefix and terms like service_role, and confirm no source map exposes it, then search your repository history since it may remain in an old commit. A scanner like PTKD.com (https://ptkd.com) analyzes your build and reports leaked keys and secrets by severity, mapped to OWASP MASVS, flagging a service_role key that reached the bundle. It does not rotate the key or configure your bundler, but it verifies what you ship is clean.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free