Guide

Supabase security checklist before you launch

By LockdownLaunch. Published October 19, 2026.

If you built your app with Lovable, Bolt, Cursor or v0 and it talks to Supabase, your browser code talks straight to your database. That’s fine and it’s how Supabase is designed to work, but it means two things decide whether strangers can read your data: which key you ship, and your Row Level Security (RLS) policies.

This is the checklist we’d run on our own app before launch. Every step is something you can check yourself in a few minutes. Only run the tests below against an app you own.

1. Make sure the right key is in your frontend

Supabase has two kinds of keys, and only one of them can be public.

KeyLooks likeOkay in browser code?
Publishable keysb_publishable_...Yes, as long as RLS is set up
Legacy anon keyLong JWT starting with eyJYes, as long as RLS is set up
Secret keysb_secret_...Never
Legacy service_role keyLong JWT starting with eyJNever

Secret and service_role keys skip RLS completely. If one of them is in your site’s JavaScript, anyone can read, change or delete every row, no matter how good your policies are. Supabase’s own docs put it simply: the publishable key only opens the lobby, the secret key is the master key.

The tricky part is that the legacy anon and service_role keys look the same at a glance. Both start with eyJ. To tell them apart, paste the key into your browser console like this and look at role:

const k = "eyJ...your key...";
JSON.parse(atob(k.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))).role
// "anon" is fine. "service_role" means stop and fix it now.

To find which key your live app ships, open it, open DevTools, go to the Network tab, and look at any request to your-project.supabase.co. The apikey header is the key your frontend uses. If you have the code locally, you can also search your build output:

npm run build
grep -rlE "sb_secret_|service_role" dist/

A legacy service_role key won’t contain the words “service_role” in plain text, since they’re base64-encoded inside the JWT. Decoding the key, as shown above, is the reliable check.

Supabase says the legacy anon and service_role keys are being deprecated by the end of 2026, so if you’re touching this code anyway, it’s a good time to move to publishable and secret keys.

2. Turn on RLS for every table

Every table in the public schema can be reached through your project’s API. If RLS is off on one of them, anyone with your project URL and public key can read and write it. Run this in the Supabase SQL editor:

select tablename
from pg_tables
where schemaname = 'public'
  and not rowsecurity;

Any table it returns has RLS off. Supabase’s Security Advisor (in the dashboard under Advisors) flags the same thing as an error. Fix it with:

alter table public.your_table enable row level security;

Once RLS is on, the public key can’t see anything in that table until you add a policy that allows it. That’s the safe default. Add policies for what your app actually needs.

If you build with Lovable, its built-in security scan also looks at this when you publish. It’s worth running, and so are the checks below, since a table can pass “RLS is on” and still be wide open.

3. Read your policies, not just the toggle

RLS being enabled doesn’t mean much on its own. A policy decides who gets which rows, and a policy can let everyone through. List all of yours:

select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;

Things to look for:

The usual pattern for “users only see their own rows” looks like this:

create policy "Users read their own rows"
on public.notes for select
to authenticated
using ((select auth.uid()) = user_id);

create policy "Users add their own rows"
on public.notes for insert
to authenticated
with check ((select auth.uid()) = user_id);

If you’d rather have your AI builder do it, a prompt like this works well: “Review the RLS policies on every table. Signed-out users should only read public data, and signed-in users should only read and change their own rows. Show me each policy before and after.”

4. Check grants, views and functions

Three things can expose data even when your table policies look right:

5. Run the logged-out test

This is the test that matters most, because it’s exactly what an attacker does. Take your project URL and public key (from step 1) and ask for a row from each table, with no user signed in:

curl "https://YOUR-PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*&limit=1" \
  -H "apikey: YOUR_PUBLISHABLE_KEY"

If you’re still on the legacy anon key, also add -H "Authorization: Bearer YOUR_ANON_KEY". A private table should come back as [] or a permission error. If it returns a row, anyone on the internet can do what you just did.

Then do the two-account test: sign up as two different users, and while signed in as the first, try to read or edit the second user’s rows (for example by changing an id in a request). You should get nothing back.

6. Lock down storage buckets

7. Look for other secrets in your bundle

Anything your frontend can read, your visitors can read too. In Vite apps (which includes most Lovable and Bolt projects), every environment variable starting with VITE_ gets copied into the JavaScript that ships to the browser. In Next.js, the same goes for NEXT_PUBLIC_.

That’s fine for your Supabase URL, publishable key, or a Stripe pk_live_ key. It’s not fine for OpenAI, Anthropic or other AI keys, Stripe sk_live_ keys, Resend or SendGrid keys, or anything else that costs money or sends email. Those belong in a Supabase Edge Function or another server-side function, and your frontend calls that function instead.

grep -rlE "sk_live_|sk-proj-|sk-ant-|sb_secret_|AKIA[0-9A-Z]{16}" dist/

8. Check for exposed files and source maps

Some hosts will happily serve files that were never meant to be public. Check these against your live domain:

curl -sI https://your-app.com/.env
curl -sI https://your-app.com/.git/config

A 200 isn’t always a problem, because many single-page apps return the home page for any path. Open the URL in your browser. If you see your app, you’re fine. If you see KEY=value lines or a [core] section, take it down and rotate everything in it.

Also check for public source maps. If your JavaScript files end with a sourceMappingURL comment and the .map file loads, anyone can read your original source code. Vite and Next.js don’t publish browser source maps in production by default, so if you have them, someone turned them on.

While you’re there, check your security headers too: Strict-Transport-Security, a Content-Security-Policy, X-Frame-Options (or CSP frame-ancestors), X-Content-Type-Options: nosniff and a Referrer-Policy. They won’t fix a leaked key, but they close off common attacks for very little effort.

9. If a secret leaked

Rotate first, clean up second. Deleting the key from your code doesn’t help if someone already copied it. Assume they did.

  1. Move the code that needs the secret to a server or Edge Function.
  2. Create a new key and deploy it there.
  3. Revoke or delete the old key in the provider’s dashboard. For Supabase, that’s Settings, then API Keys.
  4. Check the provider’s usage or billing page for anything you don’t recognize.
  5. If the key was ever committed to git, it’s still in your history even after you delete the line, so treat it as public forever.

10. What our free scan checks, and what it doesn’t

The LockdownLaunch free scan looks at your live URL from the outside, the same way a browser does. It checks:

It doesn’t touch your database, so it can’t tell you whether your RLS policies actually work. That’s what steps 2 to 6 are for. It also doesn’t see your server code, your repo, or anything behind a login, and a clean result isn’t a security guarantee.

The $49 Launch Report goes one step further once you verify that you own the app: it checks whether your Supabase tables can be read with your public key (it only reads row counts, never row contents), whether storage buckets can be listed, and the equivalent Firebase rules. It isn’t open yet. You can get an email when it opens.

Check your live app in about a minute

Free, read-only, and no signup. It won’t replace the checks above, but it looks for the leaked keys and exposed files that are easy to miss.

Run the free scan

Sources: Supabase docs on API keys, Row Level Security, Database Advisors and Storage access control; Vite docs on env variables; Lovable docs on security.