Supabase RLS checker

Supabase RLS checker for Lovable and Cursor apps

Use this manual checker before launch if your Lovable, Cursor, Bolt, Replit, or AI-built SaaS stores private data in Supabase.

A Supabase RLS checker should answer one practical question: can the wrong user access private data before your app goes live? It is not enough to see a green dashboard, a working login, or a clean demo. You need to check tables, policies, storage, service role usage, and real User A / User B behavior.

This checklist is written for AI-built apps where Lovable, Cursor, Bolt, Replit, v0, or another assistant generated much of the Supabase integration. Those tools can make the happy path work quickly, but they do not always prove that production data isolation is safe.

Launch rule: do not trust a Supabase app until a normal User B fails to read, update, delete, list, download, or overwrite User A's private data through every path your product exposes.

What should a Supabase RLS checker check?

A useful checker should cover more than whether RLS is enabled. Supabase's RLS docs say RLS must be enabled on tables in exposed schemas, including the default public schema, and that after RLS is enabled, no data is accessible through the API using a publishable key until policies allow it.

That gives you the starting point. A launch checker goes further:

  • Which tables are exposed to the browser?
  • Which private tables have RLS disabled?
  • Which policies are too broad for private data?
  • Can User B access User A's rows by guessing IDs?
  • Can User B insert or update rows with User A's ownership values?
  • Are storage buckets protected separately from database tables?
  • Are service role or secret keys kept out of frontend code?
  • Do backend routes using elevated access enforce authorization?

Step 1: Check every exposed table has RLS enabled

Start in the database. The UI can hide data even when the API is unsafe. Run a table-level check and look for private tables where RLS is disabled.

select
  c.relname as table_name,
  c.relrowsecurity as rls_enabled
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r'
  and n.nspname = 'public'
order by c.relname;

Private customer data, documents, messages, profiles, subscriptions, teams, files, usage records, credits, and workspace settings should not live in exposed schemas without RLS and a clear reason.

Step 2: Check policies, not only RLS status

RLS enabled with no policy blocks API access by default. That is safe but may break the app. RLS enabled with a broad policy can make the app work while leaking private rows. That is the dangerous middle.

select
  schemaname,
  tablename,
  policyname,
  cmd,
  roles,
  qual,
  with_check
from pg_policies
where schemaname = 'public'
order by tablename, policyname;

For private tables, inspect policies for broad expressions like USING (true), missing ownership checks, missing tenant checks, or update policies that do not protect the new row with WITH CHECK.

Step 3: Test User A / User B isolation

This is the core checker. Create two normal users and try to break isolation deliberately. User A should only see User A's data. User B should only see User B's data.

  1. Create User A and User B as normal accounts.
  2. Create private rows for each user in every private table.
  3. Login as User B and try to read User A's rows in the UI.
  4. Try direct Supabase API calls using User A row IDs.
  5. Try inserts with forged user_id, workspace_id, or organization_id.
  6. Try updates that move a row into another user or workspace.
  7. Try deletes against another user's records.

If any forbidden action succeeds, this is not an SEO problem or a warning to ignore. It is a launch blocker.

Step 4: Check auth.uid() and logged-out behavior

Supabase documents that auth.uid() returns null when there is no authenticated user. Policies like auth.uid() = user_id fail for logged-out users because null = user_id is false in SQL. That is usually good, but you should make unauthenticated behavior explicit in your tests.

Check logged-out requests for every private table, storage path, RPC function, and backend route. A logged-out visitor should not receive private user, workspace, billing, or file data.

Step 5: Check INSERT and UPDATE with WITH CHECK

Supabase's policy docs separate USING and WITH CHECK. For inserts, WITH CHECK makes sure the new row satisfies the policy. For updates, USING checks whether the existing row can be updated, and WITH CHECK checks the resulting row.

This matters for AI-built apps because forms often trust hidden fields. A user can edit request bodies in devtools. Your checker should try forged ownership values:

  • Insert a row with another user's user_id.
  • Insert into another workspace or organization.
  • Update your own row to point at another owner.
  • Upgrade your own role, plan, credits, or admin flag directly.
  • Move a file, invite, subscription, or generated report into another tenant.

Step 6: Check Supabase Storage separately

Storage has its own access-control surface. A table can pass RLS while private files are still listable, downloadable, overwriteable, or deletable by the wrong user.

Run these checks:

  1. User A uploads a private file.
  2. User B tries to list the bucket or folder.
  3. User B tries to download User A's file by path.
  4. User B tries to overwrite the same path.
  5. User B tries to delete the file.
  6. Signed URLs expire and are not reused as permanent public links.

Step 7: Check service role and secret key usage

A manual Supabase RLS checker must look for service role and secret key exposure. Elevated backend keys are useful in trusted server code, but they do not belong in frontend bundles, public environment variables, GitHub history, screenshots, logs, or generated client code.

Search for:

  • service_role
  • sb_secret_
  • SUPABASE_SERVICE_ROLE
  • NEXT_PUBLIC_, VITE_, or PUBLIC_ variables carrying backend secrets
  • Supabase keys inside built JavaScript assets or source maps

If an elevated key was exposed, rotate it and remove the exposure path. Do not only delete the current line from source code.

Step 8: Check RPC, Edge Functions, and backend routes

Even good RLS policies can be bypassed by surrounding code if a backend function uses elevated access and returns the wrong data. This is common in AI-built SaaS apps where a function was added quickly to make a feature work.

Check each function that reads private data, writes records, processes files, triggers AI jobs, updates billing state, or manages teams. The function should verify the caller, workspace, role, and billing state before doing privileged work.

Step 9: Run Supabase advisors and testing tools

Supabase provides database advisors for performance and security issues, and the CLI supports database testing and linting workflows. Use those tools as part of the checker, but do not treat them as the whole audit.

Automated checks are good at catching mechanical issues. They do not always know your product's real authorization model. A scanner may flag missing RLS, but it cannot always prove that a workspace owner, member, canceled user, or invited user has exactly the right access.

Supabase RLS checker checklist

  1. Every private table in exposed schemas has RLS enabled.
  2. Policies exist for the actual operations the app uses.
  3. Private policies are not broad USING (true) policies.
  4. INSERT and UPDATE policies use WITH CHECK where ownership can change.
  5. User A / User B tests fail for forbidden reads, inserts, updates, deletes, and list queries.
  6. Logged-out requests fail for private resources.
  7. Storage buckets are checked for list, download, upload, overwrite, and delete paths.
  8. Service role and secret keys are not exposed to browser code or public history.
  9. RPC functions, Edge Functions, and backend routes enforce authorization.
  10. Supabase advisors, linting, and database tests are clean or understood.

Launch verdict

Block ship if the checker finds disabled RLS on private tables, broad private policies, exposed service role keys, or User B access to User A data.

Ship with fixes if tables pass but storage, RPC, Edge Functions, backend routes, or automated tests are still unverified.

Clear to ship when table policies, storage access, logged-out behavior, User A / User B isolation, secret handling, and backend routes all pass.

Useful references

Want a manual Supabase RLS checker before launch?

I review Supabase RLS, storage policies, service role exposure, backend routes, and User A / User B isolation in AI-built SaaS apps.

Book $299 Launch Audit View sample report