Guides

Supabase RLS for Vibe-Coded Apps: Is Your Anon Key Safe?

By ExcenityAI · Published · Updated

Your Supabase anon (publishable) key is meant to be in the frontend, but it is only safe if Row Level Security (RLS) is enabled on every table in an exposed schema, with policies that limit each user to their own rows. If RLS is off, anyone who copies that key from your site can query your database. The service-role (secret) key must never be in client code.

What is Row Level Security?

RLS is a Postgres feature that decides, row by row, who can read or change data. Supabase exposes your database over an API that the browser calls directly using the anon key, so RLS is the thing that stops one user seeing another's rows.

Supabase's documentation puts it plainly: enable RLS on every table in an exposed schema. With RLS on and no policies, the API returns no data. That is safe, but your app will look broken, so you then add policies.

How do you check whether RLS is on?

  1. In the Supabase dashboard, open the Table Editor. Tables without RLS are flagged.
  2. Or run this in the SQL editor to list tables and their RLS status:
select schemaname, tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by tablename;

Every row should show rowsecurity = true. Then test from outside: call your REST endpoint with only the anon key and no login, and confirm you get nothing you should not see.

How do you enable RLS and write a first policy?

A common pattern is a table where each row belongs to a user. Example schema, with a user_id column:

alter table public.notes enable row level security;

create policy "read own notes"
on public.notes for select
to authenticated
using ( (select auth.uid()) = user_id );

create policy "insert own notes"
on public.notes for insert
to authenticated
with check ( (select auth.uid()) = user_id );
  • Write a separate policy for each of select, insert, update and delete. Use using to filter existing rows and with check to validate new or changed rows.
  • Always add a to clause such as to authenticated, so the policy does not apply to roles you did not intend.
  • auth.uid() returns null when nobody is logged in, so be explicit about unauthenticated access.
  • Wrapping it as (select auth.uid()) lets Postgres cache the value for the statement, and Supabase recommends indexing the columns your policies filter on.

What mistakes do AI tools make with RLS?

  • RLS never enabled. The table works in the preview, so nobody notices.
  • Policies like `using (true)`. This makes the row public to the role. It passes a quick test but protects nothing.
  • Trusting user metadata. Supabase warns that users can edit user_metadata, so do not base authorization on it. Use app_metadata for roles, and remember tokens can be stale until refreshed.
  • Service-role key in the client. The secret key bypasses RLS when no user token is sent. Keep it on the server only.
  • Views that skip RLS. On Postgres 15 and later, create views with security_invoker = true, otherwise they can bypass your policies.
  • Default grants left open. Supabase notes new tables in public can start with broad privileges for anon and authenticated. Revoke what you do not need. Grants and policies are separate checks.
  • Recursive policies. Two tables whose policies reference each other can fail with error 42P17. A carefully written security definer function, with search_path set to empty, breaks the loop.

How do you test your policies?

  1. Create two users and insert rows for each.
  2. Log in as user A and confirm you can read, update and delete only A's rows.
  3. Try to read B's row by ID. It should return nothing.
  4. Call the API with no login and confirm private tables return nothing.
  5. For a repeatable check, Supabase suggests pgTAP tests under supabase/tests/, run with supabase test db.

Re-run these tests whenever the AI tool changes your schema, because a regenerated migration can drop a policy.

What else should you check?

RLS covers database rows. Also check storage bucket policies, Edge Function permissions and where your secret keys live. Our security guide and production checklist cover those. If you want someone to review your policies with you, the free audit call includes Supabase RLS.

Frequently asked questions

Is my Supabase anon key in the frontend safe?

Yes, but only if RLS is enabled on every table in an exposed schema and your policies are correct. The anon key is designed to be public. It is the policies, not the key's secrecy, that protect your data.

What happens if I enable RLS but add no policies?

Requests through the public API return no rows. That is safe by default, but your app will not show data until you add policies for the actions each user needs.

Can the service-role key go in my frontend?

No. It is a secret key that bypasses RLS when requests carry no user token. Keep it in server-side code and environment variables only, and rotate it if it was ever exposed.

Does RLS protect files in Supabase Storage?

Storage has its own access policies. Check each bucket and make private files private, served through signed URLs.

Can ExcenityAI review my RLS policies?

Yes. RLS is part of the free 30-minute audit call. We look at your policies together, then send findings, a fix plan and a quote.

Sources

Checked 27 Oct 2026. Provider settings and limits change, so confirm against the linked documentation.

Keep going

Want a second pair of eyes? Book the free audit call.

A free 30-minute review of your AI-built app, then a written fix plan and a quote.