If Lovable put your Supabase service_role key in the client, it is a critical exposure that needs an immediate response and an architecture fix. The service_role key bypasses Row Level Security and gives full read and write access to your database, so anyone who extracts it from your frontend or repository has complete control. Rotate it right away, then fix the real problem: the service_role key must live server-side, in a Supabase Edge Function or your own backend, never in your Lovable app's client code. The client should use only the public anon key, protected by RLS.
Short answer
The service_role key is a server-side admin key, so the fix is to get it out of the client and keep it on a backend. Per Supabase's API keys documentation, it bypasses Row Level Security and has full data access. First rotate it, because the exposed copy is compromised. Then move any privileged operation into a Supabase Edge Function, where the key is available as a secret environment variable and never reaches the browser. The client keeps only the anon key, and RLS protects your data. Finally, remove the key from your code and Git history, and verify with the anon key that your tables are not exposed.
Confirm it is the service_role key
Before you act, confirm which key leaked, because the response is very different. Supabase gives you a public anon (publishable) key, which is meant to ship in the client, and a secret service_role key, which is a full-access admin key. If what leaked is the anon key, that alone is normal and your real concern is whether RLS is enabled. If it is the service_role key, treat it as an emergency.
You can tell them apart by role: the service_role key is labeled as the secret or service role key in your Supabase dashboard, and it is the one that carries admin rights. If a Lovable-generated app is calling your database directly from the frontend with the service_role key, that is the exposure to fix, and everything below applies.
Step 1: rotate the key immediately
Rotating is the first move, because it is the only action that invalidates the copy an attacker may already have. In Supabase, with the newer API keys you revoke the specific secret key and issue a new one, and Supabase can automatically revoke secret keys it detects in public GitHub repositories. With the legacy keys, rotating the service_role key means rolling the JWT secret, which regenerates it and invalidates existing tokens.
Rotate before you refactor. Even a perfect architecture fix does nothing about the key that already leaked, so kill the old key first, then rebuild the flow so it does not happen again. Assume the exposed key was captured, and review your database logs for unusual access around the time of the leak.
Step 2: the real fix, move it server-side
Rotation stops the bleeding; the real fix is architectural. The service_role key exists for privileged, server-side operations, so it must live on a server, not in an app you ship to users. The table below shows where each key belongs.
| Key | Belongs in | Never in |
|---|---|---|
| anon / publishable | The client, protected by RLS | (it is safe to expose) |
| service_role / secret | A backend or Edge Function | The client, repo, or bundle |
| JWT / signing secret | Supabase, server-side only | Anywhere client-side |
The principle is simple: anything shipped to a client can be extracted, so a secret in your Lovable frontend is not a secret. Privileged work moves to a backend the user cannot read, and the client is left with only the public key, which is safe because RLS governs it.
Using a Supabase Edge Function to hold the service_role key
A Supabase Edge Function is the natural place for the service_role key in a Lovable and Supabase app. Edge Functions run on Supabase's servers, and the service_role key is available to them as the SUPABASE_SERVICE_ROLE_KEY environment variable, which Supabase documents as safe to use there but never in a browser. Your client calls the function, the function performs the privileged operation with the key, and the key never leaves the server.
The refactor is to identify every place your app used the service_role key directly, such as an admin write or a cross-user query, and move that logic into an Edge Function. The client then calls the function over HTTPS, ideally with the user's own auth token, and the function does the privileged work. This is the same backend-for-frontend pattern that protects any secret; Edge Functions just make it easy inside Supabase.
Storing the key as a secret, not in code
Even server-side, do not hardcode the key. Supabase provides secrets management for Edge Functions, through the CLI secrets commands or the Edge Function Secrets page in the dashboard, so keys and other secrets are stored securely and injected as environment variables at runtime. The built-in SUPABASE_SERVICE_ROLE_KEY is provided automatically, and any additional secrets you need go in the same secret store.
Keeping the key in a managed secret, rather than in a file, means it never appears in your code or your repository, which is where leaks start. It also makes rotation cleaner, because you update the secret in one place instead of hunting through source. Treat the secret store as the only home for the key, and keep it out of both the client and the codebase.
The client uses only the anon key, with RLS
With the service_role key server-side, your client uses only the public anon key, and Row Level Security is what makes that safe. RLS is off by default on new tables, so you must enable it on every table and write policies matching your auth logic, such as letting each user read only their own rows. Then the anon key, even though it is public, can only reach data your policies allow.
This is the whole security model of a Supabase client app, and it is what a Lovable-generated project often skips. The client with the anon key and RLS handles normal operations; the Edge Function with the service_role key handles the privileged ones. Get this division right and the exposed-key problem cannot recur, because there is no secret in the client to expose.
Remediation checklist
Work the fix in order, from stopping the leak to verifying it is closed. The checklist below covers it.
| Step | Action | What it confirms |
|---|---|---|
| 1 | Rotate the exposed service_role key | The old key is dead |
| 2 | Move privileged calls to an Edge Function | The key leaves the client |
| 3 | Store the key as a Supabase secret | It is not hardcoded |
| 4 | Enable RLS so the client uses only anon | Data is protected by policy |
| 5 | Remove the key from code and Git history | No re-leak of the old key |
| 6 | Query your tables with the anon key | The fix is verified |
The two steps people skip are the last two: removing the key from Git history, since a later commit does not erase earlier ones, and actually testing with the anon key that your data is protected. Both are quick and both are what confirm the exposure is truly closed.
Verify the fix
After remediation, verify rather than assume. Confirm the old service_role key is revoked and no longer works, check that no client file or the shipped bundle contains the key, ensure RLS is enabled on every table, and then run the attacker's own test: query your tables with the public anon key and confirm it returns only what your policies allow.
That last check is the direct proof. A scanning bot or an attacker will try exactly that, so if your anon key cannot reach data it should not, and the service_role key is gone from the client and rotated, the exposure is closed. Keep the verification as a step you repeat after future changes, because a new privileged call could reintroduce the same mistake.
Scan the build before you ship
Because AI builders like Lovable make it easy to ship a secret by accident, checking the build before release catches a committed or bundled service_role key while it is still cheap to fix. A key in the frontend is exactly the pattern automated attackers hunt for, so finding it first keeps you ahead of them.
A scanner like PTKD.com analyzes your mobile build and returns findings ordered by severity and mapped to OWASP MASVS, including embedded secrets and insecure network and storage patterns, so an exposed key does not reach users. To be clear about the boundary: a build scanner checks the app you ship, not your Supabase dashboard, so it does not replace moving the key to an Edge Function, rotating it, or enabling RLS. It catches the client-side exposure; the server-side architecture is yours to fix.
What to take away
- An exposed Supabase service_role key in a Lovable app is critical: it bypasses RLS and gives full database access.
- Rotate the key first, because the exposed copy is already compromised.
- Move the service_role key server-side, into a Supabase Edge Function, and store it as a secret, never in client code.
- Leave the client with only the anon key, and enable RLS on every table so that key is safe.
- Scan each build with PTKD.com so a service_role key never ships to users again.




