Supabase RLS Checklist: 12 Things to Verify Before Launch
Supabase's Data API can expose database tables through public endpoints. Whether a request can reach data depends on the API's exposed schemas, schema and table privileges, the request's database role, and row-level security (RLS). Review these layers together before launching an app built with Lovable, Bolt, or any other development tool.
This checklist is a manual review guide. It is not evidence that a particular app is secure, and it does not mean Sentrail automatically verifies every item below.
1. Inventory tables and RLS settings
select relname, relrowsecurity
from pg_class
where relnamespace = 'public'::regnamespace and relkind = 'r';A false result means row-level security is disabled; it does not by itself prove anonymous access. Check whether the schema is exposed through the API and whether anon or authenticated has the required schema and table privileges. If those permissions allow access, a table without RLS has no row-policy filter protecting its rows. Repeat the inventory for every exposed schema, including partitioned tables where applicable.
2. Review policies and intentional default denial
select c.relname, count(p.polname) as policies
from pg_class c left join pg_policy p on p.polrelid = c.oid
where c.relnamespace = 'public'::regnamespace and c.relkind = 'r'
group by c.relname order by policies;For roles subject to RLS, an enabled table with no applicable policy uses default deny. That can be intentional. Test intended reads and writes before enabling RLS in production. Table owners normally bypass RLS unless forced; superusers and roles with BYPASSRLS also bypass it. Do not test customer isolation only through a privileged SQL connection.
3. Inspect permissive expressions and combined policies
using (true) allows every row for the policy's applicable operation and roles, subject to ordinary privileges and other applicable policy rules. It can be appropriate for intentionally public content, but usually needs investigation on private user data. Permissive policies combine with OR; restrictive policies combine with AND. Review the combined outcome rather than one policy in isolation.
4. Name the intended database role
Use to authenticated or deliberately to anon when that matches the design. A policy without a role list applies to PUBLIC, meaning all roles. Being authenticated is not sufficient to authorize access to another user's or tenant's rows.
5. Understand USING and WITH CHECK
USING determines which existing rows a role can see or target. WITH CHECK validates the proposed row for an insert or update. For ALL and UPDATE policies, PostgreSQL uses the USING expression as the check when WITH CHECK is omitted. Therefore an ALL policy with using (user_id = auth.uid()) does not automatically permit inserting another user's row simply because it omits WITH CHECK.
A SELECT policy grants no insert permission. An INSERT policy uses WITH CHECK, not USING. Review each operation explicitly, including the SELECT policy needed for typical updates, and test attempts to change ownership or tenant membership. Explicit checks can improve readability, but must match the intended business rules.
6. Grant only required privileges
RLS supplements ordinary privileges; it does not replace them. Inventory schema usage, table operations, column privileges and function execution permissions. Grant only operations the feature requires. Do not copy broad GRANT ALL or write privileges into a read-only feature. Review default privileges so newly created objects do not accidentally widen access.
7. Protect authorization data from user edits
Store roles and tenant membership where clients cannot promote themselves. A separate roles table is a useful design, but is not automatically safe: protect its writes and test its policies. If profile rows include authorization fields, prevent users from changing those fields using suitable column privileges or a controlled server operation.
8. Use SECURITY DEFINER only when necessary
Prefer the default invoker behavior. A policy that queries its own protected table can recurse; redesigning the lookup or using a carefully restricted helper may resolve this. A SECURITY DEFINER function runs with its owner's privileges and needs a separate security review. Use a fixed safe search_path, fully qualified object names, least-privileged ownership, deliberate EXECUTE grants and inputs that cannot authorize an arbitrary user. Supabase recommends keeping security-definer helpers outside API-exposed schemas. It is not a blanket requirement for all role checks.
9. Review view permissions
Views can use their owner's permissions and bypass protections expected by the caller. Where supported (PostgreSQL 15+), security_invoker = true makes underlying access use the caller's permissions. Also review grants and API exposure. Test the view through the same role as the application.
10. Check Storage separately
Review policies on storage.objects, bucket visibility and each operation. Public buckets permit public file retrieval, while uploads, deletes and other operations still need appropriate controls. Scope private objects to the intended user or tenant and test cross-tenant path access. Database-table policies do not automatically secure Storage objects.
11. Keep privileged keys server-side
Publishable/anon keys are intended for clients; they are not authorization boundaries. Secret/service-role keys are privileged and must stay out of client bundles, logs and public repositories. If exposed, rotate them and investigate their use. Customer tests must use customer credentials, not an administrative key that bypasses RLS.
12. Verify after changes with multiple identities
Test unauthenticated access, user A, user B and appropriate tenant roles. Cover SELECT, INSERT, UPDATE and DELETE, including changing an ownership field. Check that legitimate access works as well as that forbidden access fails. Use synthetic data in a controlled environment and record the schema revision, test time, role and observed result. Recheck after migrations, grant changes and policy edits. Creating a table through SQL does not automatically enable RLS.
Fix order and verification
First investigate publicly reachable sensitive data, then cross-user reads and writes, excessive grants, privileged functions and Storage. Review sibling objects created by the same migration. Rerun affected checks after applying a fix: an applied change is not a verified result. Missing checks remain missing evidence.
Sentrail supports Supabase security review and evidence collection. See the Supabase integration guide, verdict methodology and illustrative sample report for setup, coverage and limitations.
Primary sources and correction
- Supabase: row-level security
- PostgreSQL: CREATE POLICY and implicit WITH CHECK behavior
- PostgreSQL: row security, default denial and bypass roles
- Supabase: securing the Data API
- Supabase: Storage access control
Correction, October 8, 2026: clarified API exposure and grants, corrected implicit WITH CHECK behavior, qualified SECURITY DEFINER guidance, and removed an unverified external prevalence statistic. This is an editorial correction against primary documentation, not a named independent expert review. Editorial standards and corrections.