Supabase Security Advisor warnings explained: what to fix first
Fix the ERROR findings first, especially "Table publicly accessible" (RLS is off), "View bypasses row-level security" and "Security policy relies on user-editable data": those can let strangers read or change data through your public API. Then check the warnings that quietly undo RLS without turning it off, such as policies that are always true (USING (true)) and SECURITY DEFINER functions anyone can call. A clean Security Advisor is a good sign, but it can't tell whether each policy matches who should see what in your app, so test as a logged-out visitor too.
Where to find the Security Advisor
In the Supabase dashboard, open Security Advisor in your project. You can also get the same list from the CLI with supabase db advisors, or through the Supabase MCP server's get_advisors tool (type security), which means an AI coder connected to your project can read it too (Supabase docs).
Each finding has a level (ERROR, WARN or INFO) and a lint name such as security_definer_view. The names below use Supabase's own summaries, so the wording in your dashboard may differ slightly. Supabase notes that some findings can be intentional, so check each one against what you meant before changing anything.
Fix these first: findings that can leak data
| Finding | What it means | Usual fix |
|---|---|---|
Table publicly accessiblerls_disabled_in_public · ERROR | A table in the public schema has Row Level Security off. Supabase says anyone with your project URL can then read, edit and delete every row. | Turn RLS on, then add policies. With RLS on and no policy, the API returns nothing, which is the safe default. |
Sensitive data publicly accessiblesensitive_columns_exposed · ERROR | The same open table, but with columns whose names suggest secrets or personal data (password, token, api_key, iban, passport_number and similar). | Turn RLS on. Ask whether you need to store that column at all, or move it to a separate protected table. |
View bypasses row-level securitysecurity_definer_view · ERROR | Postgres views run with their creator's permissions by default, so a view in public can show rows your RLS policies hide. | Make the view respect RLS: security_invoker = on (Postgres 15+). |
User data exposed through a viewauth_users_exposed · ERROR | A view reads from auth.users (every user's email and sign-in details) and makes it reachable through your API. | Use a profiles table filled by a trigger, or a security-invoker view, as Supabase's docs describe. |
Security policy relies on user-editable datarls_references_user_metadata · ERROR | A policy trusts user_metadata, which any signed-in user can change for themselves, for example to is_admin: true. | Keep roles somewhere users can't write, such as a roles table only your server updates. |
If you use Supabase Queues, also fix Queue exposed without protection (insecure_queue_exposed_in_api) the same way: enable RLS on the queue's table.
Warnings that are often real holes
These don't turn RLS off, but they can make it pointless. A WARN or INFO label doesn't mean "safe to ignore".
- Security policy allows unrestricted access (
permissive_rls_policy). A policy withUSING (true)orWITH CHECK (true)lets everyone with that role read or write every row. RLS shows as "enabled", but the door is open. Fine for genuinely public, read-only data; not for anything per-user. - Security policy not enforced (
policy_exists_rls_disabled, INFO). You wrote policies but never turned RLS on, so they do nothing. If the table is inpublic, it's open. - SECURITY DEFINER function callable without signing in, or by signed-in users (
anon_security_definer_function_executable,authenticated_security_definer_function_executable). Such a function runs with its owner's rights, usually a role that bypasses RLS, and anyone with the role can call it at/rest/v1/rpc/. If sign-up is open, "signed-in users" can mean anyone. - Multiple permissive policies (
multiple_permissive_policies). Permissive policies combine with OR, so one loose policy opens rows that a strict one was meant to protect. - Anonymous sign-ins enabled (
auth_allow_anonymous_sign_ins). Anonymous users get the sameauthenticatedrole as real accounts, so a policy written "for logged-in users" applies to them too. - Materialized view or foreign table in the API (
materialized_view_in_api,foreign_table_in_api). These can't be protected by RLS at all. If the API doesn't need them, revoke access fromanonandauthenticated. - Public bucket allows listing (
public_bucket_allows_listing). Clients can list every file in a public Storage bucket, not just open the links you share. Remove the broad select policy.
Lower risk, still worth a minute
- Function search path mutable (
function_search_path_mutable): addset search_path = ''to the function and use full names likepublic.profilesinside it. - Extension in public schema (
extension_in_public): move it withalter extension name set schema extensions; - Object visible in your GraphQL schema (
pg_graphql_anon_table_exposedand the signed-in version): table and column names are visible, not rows. If you don't use GraphQL, Supabase suggests turning off thepg_graphqlextension. - No access rules defined (
rls_enabled_no_policy): safe, because nothing is readable through the API, but parts of your app may not work until you add a policy.
Check it yourself in the SQL editor
These queries only read Postgres's own catalog; they don't change anything. Run them in the SQL editor of your project.
What a clean Security Advisor doesn't tell you
The Advisor looks for known risky patterns. It doesn't know your app, so a green list can still hide problems:
- Wrong rules that look fine.
auth.uid() = user_idpasses every check, but it's wrong if the column means something else, or if team members should see each other's rows and strangers shouldn't. - A secret key in your frontend. The
service_roleorsb_secret_key skips RLS entirely. If it's in your JavaScript bundle, your policies don't matter (see API keys in the frontend). - Your own API routes and Edge Functions. Server code often uses the secret key, so it has to check who the user is itself.
- Data sent with the page. A login screen doesn't protect data that's already in the HTML or JavaScript a logged-out visitor downloads.
So after the Advisor is clean, test from the outside too: open a private window and try to read each table using only the public key. Our Supabase anon key guide shows how. Real cases where weak RLS led to exposed data are in our security tracker.
The 30 October 2026 change, in one paragraph
According to Supabase's changelog, from 30 October 2026 existing projects stop exposing new tables in the public schema to the Data API automatically: each one needs an explicit grant first. Tables you already have keep their current grants and stay reachable, and RLS works exactly as before. So the change protects tables created after that date, not the ones you have now. If your AI tool creates tables, ask it to add the grant, enable RLS and add a policy in the same migration; without the grant, the API returns a "permission denied for table" error (code 42501) with a hint showing the grant to add.
Copy-paste fix prompts
FAQ
Is a clean Supabase Security Advisor enough to launch?
It's a good start, not the whole story. The Advisor checks your database setup for known risky patterns. It can't tell whether each policy matches who should see what in your app, whether your frontend ships a secret key, or whether your own API routes check the user. Also test your tables as a logged-out visitor, using only the public key.
What does "Security Definer View" mean in Supabase?
By default, a Postgres view runs with the permissions of the user who created it, so a view in the public schema can return rows your RLS policies would hide. On Postgres 15 and later, run alter view public.your_view set (security_invoker = on); so the view respects the RLS policies of the tables it reads.
Is USING (true) always wrong in an RLS policy?
No. It's fine for data that is meant to be public and read-only, like published blog posts or a product catalogue. It's wrong for per-user data, and for insert, update or delete policies, because it lets everyone with that role read or change every row.
Does Supabase's 30 October 2026 change fix my existing tables?
No. From 30 October 2026, new tables created in the public schema of existing projects aren't reachable through the Data API until you grant access. Tables you already have keep their current grants, so they're only as safe as their RLS setting and policies.
Can I ignore "Function Search Path Mutable"?
It's lower risk than the RLS and view findings, so fix those first. It's still a quick fix: add set search_path = '' to the function and use schema-qualified names such as public.profiles inside it.
Sources
- Supabase: Advisors (every Security Advisor check, with fixes)
- Supabase: Row Level Security
- Supabase: Securing your API
- Supabase changelog: Tables not exposed to Data and GraphQL API automatically
- PostgreSQL: CREATE VIEW (security_invoker)
- PostgreSQL: ALTER VIEW
- PostgreSQL: pg_policies
- PostgreSQL: pg_tables
This guide is general information, not a security audit. Platforms change, so check the linked docs for current details. Spotted something out of date? Email hello@milokits.com.
More guides
- Is my Supabase anon key exposed? What's safe to show and what isn't
- Lovable, Bolt, Replit and v0 app security checklist before launch
- Is my API key exposed in my JavaScript? VITE_ and NEXT_PUBLIC_ explained
- Security headers for vibe-coded apps: copy-paste Netlify and Vercel configs
- Firebase security rules mistakes in AI-built apps (and how to fix them)