VibeCPR

← All fixes

Supabase · RLS

Fix Supabase RLS policy USING (true) that makes a table publicly readable

Updated · Applies to: Supabase, Lovable, Bolt, v0, Cursor, Replit, Claude Code

TL;DR fix: A policy like USING (true) or WITH CHECK (true) for anon or authenticated means every row is allowed for that operation. With a grant on the table, anyone who has your public anon (publishable) key can read or write the whole table through the Data API. Keep RLS on, drop the always-true policy, and replace it with a scoped check such as (select auth.uid()) = user_id. USING (true) on SELECT is only OK for data that is meant to be public.

Steps

  1. Confirm the table is reachable with the anon key. Your anon (publishable) key is meant to be public. The risk is the table behind it. In the browser Network tab or with curl, a GET to /rest/v1/<table>?select=* with only the anon key returning rows for a private table means the grant plus policy combo is too open.
  2. Open Security Advisor and list policies. In the Supabase dashboard go to Advisors → Security Advisor. Fix flags for RLS disabled and permissive policies (always-true). Also open Authentication → Policies (or the table in Table Editor) and read every USING / WITH CHECK expression.
  3. Find USING (true) and WITH CHECK (true). Any policy whose USING or WITH CHECK is literally true (or a tautology like 1=1) allows every row for that operation and role. On INSERT, UPDATE or DELETE for anon, that is almost never intentional. On SELECT it is only OK for genuine public catalogs.
  4. Drop the always-true policy and write a scoped one. Keep RLS enabled. Drop the bad policy, then create one that checks (select auth.uid()) = user_id (or your ownership column) for authenticated. Grant anon SELECT only when the data is meant to be public, and only with an intentional public-read policy.
  5. Retest as anon and as a signed-in user. Logged out: private tables should return empty or 401/403, not other users' rows. Logged in: you should see only your own rows. The SQL editor bypasses RLS, so always retest through the app or REST with the anon key.
  6. Re-run Security Advisor. Confirm the permissive-policy and RLS-disabled warnings are gone, then load the live app once more as a logged-out visitor.

The fix in SQL

Run this in the Supabase SQL editor. Replace profiles and user_id with your table and owner column. Keep RLS on.

-- Keep RLS enabled (do not disable it)
alter table public.profiles enable row level security;

-- Remove always-true policies (adjust names to match yours)
drop policy if exists "Allow public read access" on public.profiles;
drop policy if exists "Enable read access for all users" on public.profiles;
drop policy if exists "Allow all" on public.profiles;

-- Signed-in users read only their own rows
create policy "users read own rows"
  on public.profiles for select
  to authenticated
  using ((select auth.uid()) = user_id);

-- Signed-in users update only their own rows
create policy "users update own rows"
  on public.profiles for update
  to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);

-- Optional: if this table must be readable when logged out (public catalog only)
-- grant select on public.profiles to anon;
-- create policy "public read catalog"
--   on public.profiles for select
--   to anon, authenticated
--   using (true);  -- intentional public SELECT only — never for insert/update/delete

Supabase’s own RLS guide states that a policy to anon using ( true ) grants every unauthenticated visitor read access to every row the role can already reach through grants, and should be used only for data that is meant to be public. Security Advisor lint 0024_permissive_rls_policy flags always-true USING / WITH CHECK expressions for the same reason.

Why AI-built apps hit this so often

Coding agents often enable RLS (or inherit it from the Table Editor), then add a placeholder policy so the app “works” on the first run: using (true) or with check (true). That clears insert errors and blank lists, and looks like a successful fix in the chat transcript. It also recreates the failure mode behind CVE-2025-48757 and the open tables UpGuard counted at 16,326 on October 14, 2026: the public anon key can reach every row.

Related footguns we already document separately:

Prompt for Cursor, Lovable or Bolt

Audit public.<table> for policies with USING (true) or WITH CHECK (true).
Do NOT disable RLS. Do NOT leave always-true write policies for anon or authenticated.
Drop those policies and replace them with (select auth.uid()) = user_id
(USING and WITH CHECK as appropriate). USING (true) is allowed only for
intentional public SELECT on non-sensitive data. Show me the SQL diff first.

When to get help

Get help if an AI tool keeps regenerating USING (true) policies, if you have many tables and are not sure which ones should be public, or if Security Advisor is clean but REST still returns other users' rows.

FAQ

What does an RLS policy with USING (true) mean?

USING (true) means the policy allows every row for that operation. Combined with a GRANT to anon or authenticated, anyone holding your public anon key can perform that operation on every row through the Data API. Supabase's Security Advisor flags this as a permissive RLS policy. Replace it with a real condition such as (select auth.uid()) = user_id, unless the table is intentionally public and the policy is SELECT-only.

Is this the same as RLS being disabled?

No. With RLS disabled, grants alone decide access and every granted role can touch every row. With RLS on and USING (true), policies exist but do not restrict rows, so the effect for that operation is the same: full table access for the role. Fix by scoping the policy, not by turning RLS off.

How do I check if my Supabase tables are publicly readable?

Run Security Advisor, then try the REST endpoint for each sensitive table with only your anon key (no user JWT). If private rows come back, the table is publicly readable. Step-by-step checklist: https://vibecpr.com/blog/check-supabase-rls-in-two-minutes

Is my anon key leaked if it is in the frontend?

No. The anon (publishable) key is designed to sit in client code. The leak is data: tables the anon role can read or write because RLS is off or a policy is USING (true) / WITH CHECK (true). Never put the service_role or secret key in the browser.

How does this relate to CVE-2025-48757 and UpGuard's findings?

CVE-2025-48757 covered Lovable apps whose Supabase tables had missing or insufficient RLS, so the public anon key could read or write data. UpGuard's October 14, 2026 study found 16,326 Supabase databases with publicly readable tables across ~300,000 domains, including cases where policies existed but did not adequately restrict access. Always-true policies are one of those inadequate patterns.

Sources

Related fixes