github

Views Bypassing RLS in Supabase

Supabase security checks · HIGH

This view runs with its creator's rights, so it skips the row level security on the tables underneath.

What goes wrong

Postgres views run with the privileges of the user who created them by default (SECURITY DEFINER). In Supabase, views created in the SQL Editor are owned by supabase_admin, a superuser, so they bypass row level security (RLS) on the tables underneath. A view that joins users and orders exposes all of that data, whatever policies those tables have.

How it happens

The view was created without security_invoker = true.

How to find it

List the views in the public schema that don’t have the security_invoker option set.

SELECT c.relname AS view_name, pg_get_userbyid(c.relowner) AS owner
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'v'
  AND n.nspname = 'public'
  AND NOT EXISTS (
    SELECT 1 FROM pg_options_to_table(c.reloptions)
    WHERE option_name = 'security_invoker' AND option_value IN ('true', 'on', '1')
  );

How to fix it

On Postgres 15 or newer, set security_invoker on the view. On older versions, revoke SELECT on the view from anon and authenticated, or move it to a schema that isn’t exposed.

ALTER VIEW public.<view_name> SET (security_invoker = true);

Check your own project

Locksoup runs these checks on your Supabase database in about a minute, with a read-only role, and gives you the SQL to fix what it finds.

Check my project