Run the database linter and read the result for lint 0013
Run the advisors for your project and look for 0013_rls_disabled_in_public, which the linter describes as a publicly accessible table. The flagged tables give you a worklist before you change anything.
Docs: supabase.com →
Repeat the failing request as anon and as an anonymous user
A request with no signed-in user runs as anon. An anonymous sign-in runs as authenticated and carries an is_anonymous claim. Note which role can read and which can write each table, so you know which policy to fix.
Docs: supabase.com →
Enable RLS on the flagged tables
Run the statement for each flagged table in an exposed schema, replacing the table name with yours. Until you add policies, no data is accessible through the API with a publishable key, so expect empty results. The next step adds policies.
alter table public.reports enable row level security;
Docs: supabase.com →
Replace open policies with row conditions
Replace any policy whose condition is true for every row. The new policy should name a role and tie each row to the requesting user. Grants control whether a role can access a table. The policy controls which rows.
create policy "Users read own rows"
on public.reports
for select
to authenticated
using ( (select auth.uid()) = user_id );
Docs: supabase.com →
Keep anonymous sessions out of write paths
If anonymous sign-ins are on, add a restrictive policy so inserts need a permanent user, and review existing policies before you rely on them. Enable CAPTCHA or Cloudflare Turnstile on the sign-in endpoint, because it can be abused to grow your database.
create policy "Permanent users can insert"
on public.reports
as restrictive
for insert
to authenticated
with check ( (select (auth.jwt()->>'is_anonymous')::boolean) is false );
Docs: supabase.com →
Revoke execute on functions that write
Functions in an exposed schema can be called with rpc, and by default the anon role can run them. Revoke execute from public and anon. Grant it back to authenticated where the app needs it, and check ownership inside security definer functions.
revoke execute on function public.hello_world from public;
revoke execute on function public.hello_world from anon;
Docs: supabase.com →
Restrict which columns users can update
RLS decides which rows a user can update, not which columns. A row-ownership update policy gives the owner access to every column. Revoke table-level update, then grant it on the columns users may change.
revoke update on table public.subscriptions from authenticated;
grant update (display_name) on table public.subscriptions to authenticated;
Docs: supabase.com →