2026-10-18
The usual Supabase security failures are disabled RLS, service-role keys in the client, and policies that trust a user-controlled column.
If Row Level Security is disabled, the anon key can read the table. AI builders turn RLS off because inserts fail during the demo and the fastest fix is to disable the check. That table is then world-readable.
Enable RLS on every table in the exposed schema, including join tables. A policy that is missing is a deny only when RLS is enabled. With RLS off, missing policies do nothing.
A policy of `org_id = auth.jwt() ->> 'org_id'` is safe only if the user cannot write that claim. A policy of `org_id = request body org_id` is not a policy. Compare `auth.uid()` to a membership table.
The service-role key bypasses RLS. It belongs on the server, in an environment variable that is not prefixed for the browser. If it appears in the Next.js client bundle, rotate it.
Log in as user A and request user B's row with the anon key. The response should be empty, not filtered in React. Add a SQL test that sets `request.jwt.claim.sub` and selects across tenants.
Storage buckets need policies too. A public bucket with signed upload URLs still leaks if the read policy is `true`.