Access finding · Software and cloud
RLS Disabled in Public: Close the Table, Then Prove Who Can Read It
Supabase flagged RLS Disabled in Public. What it means, how to enable row level security, and how to prove one customer can't read another's data.
What the Security Advisor said
RLS Disabled in Public0013_rls_disabled_in_publicTable publicly accessibleRow Level Security disabledSupabase exposes the tables in your public schema through its APIs, and the publishable key your app ships in the browser can call them. Row level security (RLS) is what decides, row by row, who may read or change data through those APIs. With RLS off, the answer is anyone: the Security Advisor rates this lint (0013) as an ERROR because the whole table can be read, edited, and deleted by anyone who has your project URL. Newer dashboards label the same lint “Table publicly accessible”.
Why it matters
On September 25, 2026, UpGuard published a study of roughly 300,000 domains with Supabase fingerprints and found 16,326 databases with tables anyone could read. More than half showed signs of personal data. Earlier, CVE-2025-48757 documented the same gap across apps generated by an AI app builder: tables reachable with the public key and no row level security in front of them.
The pattern is common in code written quickly, by people or by coding assistants. A table created through SQL or a migration, the route most coding tools take, starts out reachable through the API until someone turns RLS on and writes a policy. The app works perfectly in testing, because nothing in normal use reveals that a stranger could read the table too.
For a software vendor, this finding also arrives through review. A customer's security questionnaire asks how one tenant is kept from another's data (CAIQ IVS-06), a pentest report cites broken access control, and an operator's supplier review asks for evidence that access rules are tested before release.
The fix
Turn RLS on for every exposed table. Run alter table public.<table> enable row level security; for each one the advisor lists. From that moment the publishable key reads nothing from the table until a policy allows it, so expect parts of the app to stop loading. That's the signal of where policies are needed.
Write policies that match how your customers are separated. In a multi-tenant app, a row belongs to an organization, and a user may read it only through a membership in that organization. A policy that only checks that someone is signed in, or that uses using (true), clears this lint and leaves every customer's rows open to every other customer. The advisor flags the second case as lint 0024; the first one it can't see.
Clear the related lints. Views that bypass row level security (lint 0010) and policies that trust user-editable metadata (lint 0015) are both rated ERROR and lead to the same exposure by another route.
Verify as the wrong person. Rerun the Security Advisor. Then request each table with no session, as a lower role, and signed in as a second customer. A refusal counts only after the rightful owner has proved they can read the same row; otherwise an empty result may just mean the table was empty. Keep the before and after results as the evidence that the finding is closed.
In practice: keeping it closed on every release
A fix made once tends to come undone. The next migration adds a table without RLS, or a new feature loosens a policy to make a screen load. Scanners that test only with the publishable key catch the first case and miss the second, because a signed-in customer reading another customer's rows looks like normal traffic.
Our Release Verification & Readiness gate derives the list of tenant data paths from your schema, so a new table is covered the day it lands. On every release it replays reads with no session, a lower role, and another tenant's id, starts each check with a positive control, and keeps known failures as must-detect cases, so the gate fails if it ever goes blind. Each release gets a GO or NO-GO recommendation with evidence mapped to the questionnaire rows you answer.
When you can't fix it
Some tables can't take strict policies on the same day: the frontend reads them directly with the publishable key, a release is due, or the table really is meant to be public, such as a product catalog. Your named owner chooses one treatment.
- Remediate by enabling RLS with tenant-scoped policies, on a dated plan table by table, starting with tables that hold personal or payment data.
- Mitigate while the policies are written. Move sensitive tables out of the exposed schema and serve them from the server, or remove a schema from the API's exposed schemas if the app doesn't use it. Revoke insert, update, and delete for the public roles so the worst case is a read.
- Accept where the data is public by design. Enable RLS anyway with an explicit read-only policy for anonymous visitors, so the intent is written in the database, and record the table, the reason, the owner, and a review date.
Supabase is a trademark of Supabase Inc. FEDLIN is not affiliated with or endorsed by Supabase.
Where it lands in the Risk Register
| Finding | Treatment | Owner | Evidence |
|---|---|---|---|
| Table in an API-exposed schema without row level security, on a named project | Remediate, mitigate, or accept (the owner decides) | Named by you | Security Advisor output before and after, plus per-role and per-tenant read tests with a positive control. Maps to CAIQ IVS-06, OWASP ASVS V8, CWE-862, NIST 800-53 AC-3, and SOC 2 CC6.1. |
Common questions
What does "RLS Disabled in Public" mean in Supabase?
It is the Supabase Security Advisor lint 0013, shown in the dashboard as "Table publicly accessible" at level ERROR. A table in the public schema, which the Supabase APIs expose, has row level security turned off. Anyone with your project URL and the publishable key that ships in your frontend can read, change, and delete every row in it.
Is it safe to have the Supabase publishable (anon) key in my frontend?
Yes, that key is meant to be public, and every visitor to your app already has it. The protection comes from row level security on each exposed table. The secret key (service_role) bypasses row level security entirely, so it belongs on the server only and never in client code.
Does turning on RLS keep my customers apart?
It closes the anonymous path. Customer separation depends on what each policy says. A policy that lets any signed-in user read every row passes the anonymous check and still shows one customer's records to another, which is the pattern behind most published leaks in apps built on Supabase. Test it by signing in as a second customer and trying to read the first customer's rows.
How do I test Supabase RLS policies?
Start with the Security Advisor in the dashboard, which checks how tables and policies are configured. Then test behavior: pgTAP tests run with supabase test db can switch roles and users and assert what each one can see. For each check, first prove the rightful owner can read the row, so an empty result for everyone else means the policy works and the table had data to protect.
What does "RLS Enabled No Policy" mean?
Lint 0008, at level INFO. Row level security is on and no policy exists yet, so the publishable key can read nothing from that table. It is safe, and usually means the app reads the table from the server, or a policy is still to be written.
A customer or operator asking how you keep their data apart from everyone else's? Tell us who is reviewing you.