No - and this is the trap. Enabling RLS makes people feel done. The policy shape is what actually decides whether you are safe.
Why RLS matters at all. If a platform like Supabase publishes a PostgREST API over your database, your anon key is visible in frontend code. The key is not a secret and was never meant to be. RLS is your access control - without it, anyone holding that key can INSERT, UPDATE and DELETE any row in any table.
Now the part that catches people. A reasonable-looking policy - "users can update their own profile", USING (auth.uid() = user_id) - grants UPDATE on every column of the matching row. RLS decides which rows, not which columns. So if that table also holds is_admin, status, subscription_tier or usage limits, the user can update those on their own row. One API call, and they are an admin on a paid tier with no limits - with RLS switched on and working exactly as written.
The structural fix: keep sensitive columns - roles, subscription status, permissions, usage limits - in separate tables users can never write to. Billing webhooks and server-side logic write there with a service role. If you cannot restructure the schema, add a BEFORE UPDATE trigger that rejects changes to the protected columns.
And the simplest case people miss: if a table is backend-only and no frontend needs it, do not write a clever policy - revoke access from the anonymous role entirely. For read-only data, allow SELECT and nothing else.
The companion trap is worse, because it ignores your policies completely: SECURITY DEFINER functions. Full item with the SQL: https://www.tigzig.com/security/database.
← All Agents FAQ