Supabase security guide

Service role key exposed in Lovable or Cursor app: what to check before launch

Use this checklist if a Supabase service_role key, secret key, or backend environment variable may have been pasted into Lovable, Cursor, Bolt, frontend code, logs, or a public repo.

If a Supabase service role key is exposed in a Lovable, Cursor, Bolt, Replit, or frontend app, treat it as a launch blocker. The risk is not that someone can merely identify your Supabase project. The risk is that an elevated backend key can bypass normal user-level protections and reach data your browser app should never be able to access.

Supabase now recommends publishable keys for public clients and secret keys for backend components. Legacy projects may still use anon and service_role JWT keys. The launch rule is the same: public keys can live in public clients when RLS is correct; elevated keys belong only in trusted backend code.

Launch rule: if a secret key or legacy service_role key was ever exposed, rotate it, remove the exposure path, verify RLS and backend authorization, then test with User A and User B before launch.

What is the Supabase service_role key?

The legacy service_role key is an elevated Supabase API key for backend use. It maps to the built-in service_role Postgres role. Supabase's API key docs describe this role as having full access to project data and using BYPASSRLS, which means it skips Row Level Security policies.

That is useful for trusted server jobs, admin tools, Edge Functions, background workers, migrations, and sync jobs. It is dangerous in a browser bundle, public mobile app, public GitHub repo, prompt transcript, support screenshot, or client-side environment variable.

Is the anon key safe to expose?

The anon key, or the newer publishable key, is meant for public application components. It identifies the app component, not the user. User authorization still depends on Supabase Auth, Postgres roles, grants, and RLS policies.

This is why the common answer "the Supabase key is public" is incomplete. Some Supabase keys are public by design. Elevated backend keys are not.

Why is an exposed service role key worse than weak RLS?

Weak RLS is already risky because User A may read User B's rows. A leaked service role key is worse because RLS may not apply to the request at all. If an attacker can use the elevated key from outside your app, your carefully written RLS policies may not protect the data path they are using.

Before launch, separate these two questions:

  • Can normal browser users access only their own rows with the publishable or anon key?
  • Can anyone find or use an elevated key that bypasses those browser-user checks?

Where do Lovable and Cursor apps usually leak keys?

AI-built apps often leak secrets through convenience, not malice. The app works in preview, the assistant asks for an environment variable, and the founder pastes whatever made the error disappear. Before launch, search every place where generated code, prompts, and deploy settings may have copied the key.

  • Frontend files that start with VITE_, NEXT_PUBLIC_, PUBLIC_, or similar public prefixes.
  • Generated client code that creates a Supabase client with a backend secret.
  • Lovable, Cursor, Bolt, or Replit project settings and prompt history.
  • GitHub commits, pull requests, forks, screenshots, and issue comments.
  • Build logs, browser console logs, server logs, and error tracking tools.
  • Public HTML, JavaScript bundles, source maps, and network requests.
  • Documentation files, setup guides, `.env.example`, and copied terminal output.

How do I check if the key is in the browser?

Do not only search the source files. Check the built app that users receive. A secret can be absent from the visible repo but still present in a deployed bundle, source map, or runtime config.

  1. Open the deployed app and inspect the JavaScript files in browser devtools.
  2. Search built assets for service_role, sb_secret_, SUPABASE_SERVICE_ROLE, and old key fragments.
  3. Check network requests for Supabase headers or query params carrying elevated keys.
  4. Search the repo history, not only the latest commit.
  5. Search logs and error tools for request headers, env dumps, and failed connection strings.

What should I do if the service_role key was exposed?

Do not just delete the line and ship. If the key was public, assume it may have been copied. The fix needs to remove the exposure and invalidate the old credential.

  1. Stop public launch or pause the risky feature.
  2. Move privileged Supabase operations to backend-only code.
  3. Rotate the leaked secret key or replace legacy service_role usage with a new backend secret.
  4. Delete the compromised key after all trusted components use the replacement.
  5. Remove the key from frontend env vars, public bundles, docs, screenshots, and logs where possible.
  6. Review tables touched while the key may have been exposed.
  7. Check whether database rows, storage objects, auth users, or billing state could have been changed.
  8. Add monitoring for unexpected admin-like reads, writes, exports, or deletes.

Can I keep using service_role from an API route?

Yes, but only if the API route is truly server-side and does its own authorization before privileged work. A backend route using a secret key should not become a public bypass around RLS.

For every privileged route, ask:

  • Does the route require a logged-in user or signed internal webhook?
  • Does it verify the user can act on this workspace, organization, or record?
  • Does it limit which columns and rows can be read or written?
  • Does it avoid returning raw privileged data to the browser?
  • Does it log enough to audit abuse without logging secrets?

What should I test before launch?

Test the actual production-like paths, not only the happy-path UI. The point is to prove that the browser cannot obtain backend power and that the backend does not hand privileged results to the wrong user.

  1. User A cannot read User B's rows using the public Supabase client.
  2. User A cannot call a backend route and receive User B's data.
  3. Logged-out visitors cannot call privileged API routes.
  4. A canceled or unpaid user cannot trigger privileged workspace actions.
  5. Storage buckets do not reveal private files through public URLs or weak policies.
  6. Old keys no longer work after rotation.
  7. GitHub history and deployed bundles no longer contain elevated key material.

Service role exposure checklist before launch

  • No service_role, sb_secret_, or backend secret in browser code.
  • No elevated key in NEXT_PUBLIC_, VITE_, or public runtime config.
  • No elevated key in public GitHub history, docs, screenshots, or issue comments.
  • Privileged Supabase operations happen only inside backend components.
  • Backend routes verify user, workspace, role, and billing state before privileged work.
  • RLS is enabled on exposed tables and tested with separate users.
  • Storage policies are checked separately from database table policies.
  • Leaked keys are rotated, removed, and verified as invalid.

Launch verdict

Block ship if an elevated Supabase key is visible in frontend bundles, public env vars, deployed JavaScript, public repos, logs, or screenshots.

Ship with fixes if the key is backend-only, but the backend route does weak authorization or returns too much privileged data.

Clear to ship when public clients use publishable or anon keys, elevated keys stay backend-only, RLS is tested with User A and User B, and leaked credentials have been rotated.

Useful references

Want this checked before users arrive?

I review Supabase keys, RLS, storage policies, backend routes, public bundles, logs, Stripe access state, and launch-blocking exposure paths in AI-built SaaS apps.

Book $299 Launch Audit View sample report