← All field notes
App Security

Is My Supabase Secure? A Practical Checklist Before You Launch

A working checklist for Supabase security: RLS policies, anon keys, service role leaks, and the mistakes I keep finding in AI-built apps.

Matthew TurleyJuly 13, 20266 min read

Somebody asked me this exact question on a Tuesday, over Slack, about twenty minutes before their app was supposed to go live. "Is my Supabase secure?" No context. No screenshot. Just the question, sitting there.

Here's the annoying answer: it depends on four or five specific things, and none of them are "did you enable the security stuff." Supabase gives you the primitives. Whether you actually wired them up correctly is a different question entirely, and it's the one that matters.

I've reviewed north of 20 AI-built apps before launch now, Lovable, Bolt, Replit, v0, Base44, Cursor output, the whole spread. Supabase shows up constantly because it's the fastest path to "working backend" for these tools. Fast is good. Fast also means the security defaults get skipped, because nobody's sitting there prompting "and also lock down row level security" in the middle of a feature request.

So here's the checklist I actually run. Not theory. This is the order I check things in, because some of these will make the rest of the audit moot if they're broken.

1. Row Level Security is not just "on"

RLS being enabled on a table means nothing by itself. I've seen tables with RLS enabled and a policy that reads USING (true). That's not a policy, that's a welcome mat. Anyone with the anon key, which is public by design, can read every row.

Check every table for policies that scope to auth.uid() or an equivalent tenant check. If a policy exists but doesn't reference the authenticated user or their org, it's decorative. I found this exact pattern in 14 of the last 20 apps I reviewed, a users table wide open because the policy checked true instead of auth.uid() = user_id.

Also check the ones that DON'T have RLS enabled at all. Supabase will let you create a table with zero protection and nothing in the dashboard screams about it unless you go looking. New tables added mid-project, especially ones an AI tool spun up to support a feature you asked for offhand, are the most likely to get missed.

2. Know the difference between anon and service_role, and where each one lives

The anon key is meant to be public. It ships in your client bundle, that's fine, that's the design. The service_role key bypasses RLS entirely. It should never touch the browser.

I still find service_role keys in client-side env vars roughly once every three or four audits. Usually it's a .env variable prefixed with NEXT_PUBLIC_ or VITE_ by habit, because that's what made the last three env vars work, and nobody stopped to think about what that prefix actually does. If it's bundled client-side, it's in your JS payload. Anyone can pull it out with dev tools open for ten seconds.

Grep your repo for SERVICE_ROLE and check every place it's referenced. It should only ever appear in server-side code, edge functions, or your backend, never in anything that ships to a browser.

3. Storage buckets get forgotten

RLS on your database tables gets some attention, even if it's wrong. Storage bucket policies get almost none. I've found public buckets holding user-uploaded documents, avatars with predictable URLs that expose a full user list, and one memorable case where a "private" bucket was actually set to public because the toggle looked identical to the policy editor and someone picked wrong.

Check bucket-level access policies separately from table RLS. They're a different permission system even though they live in the same project, and AI coding tools frequently handle them inconsistently, locking down the database while leaving storage wide open, or vice versa.

4. Auth triggers and functions running as the wrong role

Postgres functions in Supabase can run as SECURITY DEFINER, meaning they execute with the permissions of whoever created them, not the caller. Useful for specific cases. Dangerous as a default, because it can quietly bypass RLS from inside a function call that looks completely normal from the outside.

If your AI tool generated database functions for things like "get user profile with related data," check whether they're marked SECURITY DEFINER and, if so, whether that's actually necessary. Most of the time it isn't, and it's just how the generated migration happened to come out.

5. API routes that reimplement auth badly

This one's not Supabase-specific but it shows up constantly alongside Supabase projects. Your RLS policies can be airtight and it won't matter if a Next.js API route accepts a userId from the request body and queries with the service_role client, no auth check, no session validation. I covered this pattern in more detail in I Reviewed 20 AI-Built Apps Before Launch. Here's What I Found Every Time., it's one of the two or three findings that showed up in nearly every single review.

The fix is straightforward once you see it: pull the user from the authenticated session server-side, never trust an ID passed in from the client, and let RLS be the actual enforcement layer rather than a suggestion your API route might or might not honor.

6. Test it like an attacker, not like a happy path

Here's the thing most checklists skip. You can read through every policy in the dashboard and still miss the gap, because the gap usually isn't in what's there, it's in what's missing.

Open an incognito window. Sign up as a brand new user. Grab that user's JWT and try hitting your API routes for data that belongs to someone else. Try querying a table directly with the anon key and see what comes back. If you can pull another user's rows without a policy stopping you, you have your answer, and it's not the good one.

I ran this exact test against a Base44 project a few months back and got back a full list of another tenant's invoices in about four minutes. The demo had looked flawless. The RLS policy existed, technically, it just checked the wrong column.

Where this fits with the bigger picture

None of this is exotic. It's the same handful of mistakes, showing up in a slightly different order every time, because AI coding tools are optimizing for "it works in the demo," not "it's still fine when a stranger pokes at it." If you want the aggregate numbers behind that claim, the AI-built apps exposure report breaks down how often each of these shows up across a larger sample, and it's more consistent than you'd expect for tools built by different companies.

If you've gone through this list and you're still not sure, or you'd rather have someone who does this daily walk your actual project instead of a generic checklist, that's what the Vibe Check Launch Risk Review is for. One option among several, not the only path, but it's a direct look at your RLS policies, your key exposure, and your auth flow before you're explaining a breach instead of preventing one.

Either way. Run the tests yourself first. Twenty minutes with an incognito window and a second test account will tell you more than reading the Supabase docs one more time.

M
Matthew Turley, Continuum

Fractional CTO and embedded technical partner. 20+ years shipping production software.

Run a free Leak Check →