Why Cursor Fixes Supabase RLS Errors by Opening the Table A developer found that Cursor and other AI code editors routinely "fix" Supabase row-level security errors by writing always-true policies like `using (true)` or by disabling RLS entirely, silently removing the only access control protecting a public anon key. The writeup cites CVE-2025-48757, published May 29, 2025, covering insufficient RLS policies in Lovable-generated apps that let unauthenticated attackers read or write database tables, a finding Lovable disputes. The recommended remedy is keeping RLS enabled and writing one policy per operation that ties rows to the signed-in user via `auth.uid()`. using true / with check true policy or by disabling RLS. auth.uid , plus a 60-second curl test with the anon key. I hit this on a small notes app last week. The insert from the browser failed with new row violates row-level security policy for table "notes" . I pasted the error into Cursor and asked it to fix it. It did. The error disappeared, the app worked, and I almost moved on. Then I read the migration it wrote. The "fix" was a policy that lets anyone, signed in or not, read and write every row in the table. The app worked because nothing was being checked anymore. The fix an AI editor typically writes for an RLS error is an always-true policy, or turning RLS off entirely. Both silence the error by removing the access control that caused it. -- What the AI wrote to "fix" the insert error CWE-863, Incorrect Authorization create policy "Enable insert for all users" on public.notes for insert with check true ; create policy "Enable read access for all users" on public.notes for select using true ; -- Or, when that did not work on the first try: alter table public.notes disable row level security; Here is why that matters. Your Supabase anon publishable key is meant to be public. It sits in your JavaScript bundle, and anyone can copy it out of devtools. Row Level Security is the only thing standing between that key and your data. A using true policy tells Postgres to let every row through. With RLS disabled, any role with a grant on the table can read and write all of it through the auto-generated REST API. The second "fix" I see a lot is worse. When the policy route gets confusing, the AI moves the insert to a client that uses the service role key and exposes it as NEXT PUBLIC SUPABASE SERVICE ROLE KEY . Supabase's own docs say that key goes through the service role role, which has the bypassrls attribute. In a public env var, it is your whole database, readable from the browser. AI editors optimize for making the error go away, and an always-true policy is the shortest diff that does it. The model sees "violates row-level security policy", pattern-matches it to "the policy is too strict", and loosens it. There is also a lot of using true in its training data. Quickstarts and tutorials use it for public lookup tables, and policy names like "Enable read access for all users" appear constantly. For a table of country codes, that is fine. For a table of user notes, invoices or chat messages, it is a data leak. This is not hypothetical. CVE-2025-48757, published May 29, 2025, describes insufficient RLS policies in apps generated by Lovable that let remote unauthenticated attackers read or write database tables. Matt Palmer, who reported it, lists exposed names, emails, third-party API keys and payment status data in his write-up. Lovable disputes the CVE, saying each customer is responsible for protecting their app's data. That dispute is the point. Whoever is responsible, the policy is what decides, and the AI wrote the policy. Keep RLS on and write one policy per operation that ties each row to the signed-in user with auth.uid . The insert error almost always means the row you sent does not have a user id matching the caller, so fix the data, not the policy. alter table public.notes enable row level security; -- Let Postgres fill in the owner so the client cannot forget or fake it alter table public.notes alter column user id set default auth.uid ; create policy "notes select own" on public.notes for select to authenticated using select auth.uid = user id ; create policy "notes insert own" on public.notes for insert to authenticated with check select auth.uid = user id ; create policy "notes update own" on public.notes for update to authenticated using select auth.uid = user id with check select auth.uid = user id ; create policy "notes delete own" on public.notes for delete to authenticated using select auth.uid = user id ; A few details that matter: using decides which existing rows a user can see or change. with check decides what a new or updated row is allowed to look like. Update needs both, or a user can move their row to someone else's user id . to authenticated keeps signed-out visitors out entirely. Supabase's docs also point out that select ... lets Postgres evaluate it once per statement instead of once per row. Supabase recommends it for performance. On the client, insert without sending user id at all: // The default fills user id from the caller's JWT const { error } = await supabase.from 'notes' .insert { body: text } ; If something genuinely needs elevated access, like an admin action or a webhook, do it in a server route or Edge Function that verifies the caller first, and read the service role key from a server-only env var with no NEXT PUBLIC or VITE prefix. Use the same key an attacker would use: your anon key, with no user session. curl "https://YOUR PROJECT REF.supabase.co/rest/v1/notes?select= " \ -H "apikey: $SUPABASE ANON KEY" \ -H "Authorization: Bearer $SUPABASE ANON KEY" If that returns rows, anyone on the internet can get the same rows. With the policies above, it returns . Repeat it for every table that holds user data, and grep your migrations for using true , with check true and disable row level security . Q: Is it safe to expose the Supabase anon key in my frontend? A: The anon key is designed to be public, but it is only safe when every table it can reach has RLS enabled with policies that scope rows to the caller. Without that, the anon key is a read and write key for your data. Q: How do I fix "new row violates row-level security policy" without disabling RLS? A: Make sure the inserted row's user id equals auth.uid , ideally by setting the column default to auth.uid , and add an insert policy with with check select auth.uid = user id for the authenticated role. Q: When is USING true acceptable? A: Only for SELECT on tables that are genuinely public, like a list of countries or published blog posts. Never for INSERT, UPDATE or DELETE, and never on a table with user data. I've been running SafeWeave https://tinyurl.com/2bkvyyfs for this. Its repo checks flag RLS being disabled supabase-rls-disabled , always-true policies supabase-rls-passthrough-policy and a service role key in a NEXT PUBLIC or VITE variable, and its read-only live check runs the same anon-key test against your deployed app to find tables anyone can read. Those checks are on the Cloud plan; the free tier shows the counts. Even without a scanner, the curl test above and a grep for using true catch most of this. The important thing is catching it before your users' data is the thing that proves it.