Supabase is the backend behind a huge share of AI-generated apps, and its two riskiest defaults are easy to miss under a deadline: Row Level Security (RLS) ships disabled on every new table, and the anon key is meant to be public and sits in your frontend bundle by design, so any table without an explicit RLS policy is readable and writable by anyone who has your site open in a browser.
Why this happens in Supabase-backed apps
Supabase gives you a real Postgres database, auth, and storage behind a public API in minutes, which is exactly why so many AI builders default to it. The tradeoff is that "minutes to a working backend" and "minutes to a secured backend" are two different things: RLS, storage policies, and function permissions are all separate steps from just standing the project up.
The anon key being public by design is itself a common source of confusion: it's not a leak, it's how the client is meant to talk to Supabase. The security boundary is supposed to be RLS, not keeping that key secret, so a table without a policy is exactly as open as the key that reaches it.
Common issues in Supabase apps
What actually shows up in a Supabase-backed app's configuration:
- Row Level Security left disabled: a new table has no RLS policy by default, which means it's fully open to anyone with the anon key.
- Service-role key used from client code: the service-role key bypasses RLS entirely; it should only ever be used server-side, and it's often the key a generated snippet reaches for during iteration because it "just works."
- Storage bucket policies broader than intended: a bucket created quickly during prototyping commonly ends up with a public or overly permissive access policy that never gets tightened once the feature ships.
- Database functions callable without an auth check: a Postgres function exposed via Supabase's auto-generated API runs with whatever permissions it was defined with, and one written for convenience during development can end up reachable by anonymous callers in production.
How to fix it
A deliberate pass through the Supabase dashboard covers most of this:
- Enable RLS on every table (Database → Tables in the dashboard) and write an explicit policy per table and operation: start from "deny everything," then allow specifically what each role needs.
- Grep your codebase for the service-role key and confirm every use is server-side only (an Edge Function, a server route), never in anything that ships to the browser.
- Review each Storage bucket's policy individually rather than trusting the project-wide default, especially for buckets holding user-uploaded content.
- Check any exposed database function for whether it should require `auth.uid()` to be set before it runs.
Frequently asked
Is the Supabase anon key a secret I should hide?
No. It's meant to be public and is designed to sit in your frontend code. The thing that actually controls access is Row Level Security on each table; the anon key alone grants no special access beyond whatever RLS policies allow.
How do I check if RLS is actually enabled on my tables?
In the Supabase dashboard, go to Database → Tables. Each table shows whether RLS is enabled, and if it is, whether any policies exist. A table with RLS enabled but zero policies is effectively fully locked (no access at all), while RLS disabled means fully open.