If you're building on Supabase with Lovable/Cursor/Bolt, three RLS pitfalls straight from the official docs that aren't obvious until they bite.
The first. RLS is enabled by default ONLY if you create a table through the Supabase Dashboard's Table Editor. If the table is created via raw SQL or the SQL editor, it is NOT enabled by default. AI codegen tools tend to create tables via SQL, so the default-on you'd expect from the dashboard doesn't apply. Worth opening the Authentication, Policies view in your project and confirming every table you can see has the "RLS enabled" badge.
The second. "RLS enabled" with zero policies is its own silent failure mode. From the docs: "Once you have enabled RLS, no data will be accessible via the API when using a publishable key, until you create policies." Which means: in dev, where you might be hitting the DB as the table owner or with the service role, everything works. In prod, where the browser uses the anon key, every read returns nothing. The app looks broken to the user but throws no error in your logs. Check that every table you read or write to in the app has at least one policy attached.
The third. Policies written as USING (auth.uid() = user_id) silently fail for unauthenticated users, because null = user_id is always false in SQL. The docs explicitly recommend USING (auth.uid() IS NOT NULL AND auth.uid() = user_id) to make intent unambiguous. The silent-fail mode reads to the user as "the page just doesn't load my data" which is hard to diagnose without checking the policy text.
Honorable mention. Views bypass RLS by default because Postgres creates them with the postgres user. A view built on top of an RLS-protected table can leak rows the user shouldn't see. Fix is WITH (security_invoker = true) on Postgres 15+, or revoke role access to the view.
The user highlights three common pitfalls related to Row Level Security (RLS) when using AI codegen tools with Supabase. These include default RLS settings when creating tables, the need for explicit policies to avoid silent failures, and the correct usage of policy conditions to handle unauthenticated users. An additional concern is raised about RPC functions bypassing RLS when defined as SECURITY DEFINER.
Quick bonus pitfall #4 since it came up a few times after I posted: RPC functions defined as SECURITY DEFINER bypass RLS entirely. They run as the function owner (usually postgres), not the calling user. Useful for admin operations, dangerous if used for normal user queries.
If you've got an RPC function that returns user data, double-check whether it's SECURITY DEFINER (bypasses RLS, runs with full privileges) or SECURITY INVOKER (respects the calling user's policies). Default is INVOKER, but a lot of AI codegen tools default to DEFINER without flagging it.
Easiest live test for all four: open SQL editor as a second user's JWT and run a SELECT on the table. If you get rows you shouldn't see, you have one of them.
If you're shipping in the next two weeks, those are the three checks worth running before you launch.
Context: I do audits and fixes for vibe-coded apps