Performance and Security Advisors

Check your database for performance and security issues

You can use the Database Performance and Security Advisors to check your database for issues such as missing indexes and improperly set-up RLS policies.

Using the Advisors

In the dashboard, navigate to Security Advisor and Performance Advisor under Database. The advisors run automatically. You can also manually rerun them after you've resolved issues.

Available checks

0001 unindexed foreign keys
0002 auth users exposed
0003 auth rls initplan
0004 no primary key
0005 unused index
0006 multiple permissive policies
0007 policy exists rls disabled
0008 rls enabled no policy
0009 duplicate index
0010 security definer view
0011 function search path mutable
0012 auth allow anonymous sign ins
0013 rls disabled in public
0014 extension in public
0015 rls references user metadata
0016 materialized view in api
0017 foreign table in api
0018 unsupported reg types
0019 insecure queue exposed in api
0020 table bloat
0021 fkey to auth unique
0022 extension versions outdated
0023 sensitive columns exposed
0024 permissive rls policy
0025 public bucket allows listing
0026 pg graphql anon table exposed
0027 pg graphql authenticated table exposed
0028 anon security definer function executable
0029 authenticated security definer function executable

Level: WARN

Summary: Security policy allows unrestricted access

Ramification: An RLS policy uses an always-true condition like USING (true), which defeats the purpose of having Row-Level Security enabled.


Rationale

Row Level Security (RLS) policies that use always-true expressions like USING (true) or WITH CHECK (true) effectively bypass the security that RLS is meant to provide. While RLS appears to be enabled on the table, these permissive policies allow unrestricted access to all rows for the specified roles.

This is a common misconfiguration that occurs when:

  • Developers create placeholder policies during development and forget to update them
  • Policies are incorrectly configured with the assumption that other policies will restrict access
  • Copy-paste errors from documentation examples

Patterns Detected

The lint identifies policies with these always-true patterns:

USING Clause (controls which rows can be read):

  • USING (true) - explicitly allows reading all rows
  • USING (1=1) - tautology that always evaluates to true
  • USING ('a'='a') - string comparison tautology
  • Missing USING clause on permissive SELECT policies

WITH CHECK Clause (controls which rows can be written):

  • WITH CHECK (true) - allows writing any row
  • WITH CHECK (1=1) - tautology that always evaluates to true
  • Missing WITH CHECK clause on permissive INSERT/UPDATE policies

Security Impact

When a permissive policy with USING (true) exists:

  • For SELECT: Any user with the specified role can read ALL rows in the table
  • For INSERT: Any user can insert ANY data into the table
  • For UPDATE: Any user can modify ANY row in the table
  • For DELETE: Any user can delete ANY row from the table

This is particularly dangerous when the policy applies to anon or authenticated roles, as it exposes data to all API users.

How to Resolve

Option 1: Add proper row-level conditions

Replace the permissive policy with one that properly restricts access:

-- Instead of: USING (true)
-- Use a proper condition:
drop policy "allow_all" on public.posts;

create policy "users_own_posts"
on public.posts
for select
using (auth.uid() = user_id);

Option 2: Use restrictive policies in combination

If you need a base permissive policy, combine it with restrictive policies:

-- Base permissive policy
create policy "authenticated_access"
on public.posts
for select
to authenticated
using (true);

-- Restrictive policy to limit access
create policy "only_published"
on public.posts
as restrictive
for select
to authenticated
using (status = 'published' or auth.uid() = user_id);

Option 3: Remove the policy if RLS is not needed

If you don't need row-level restrictions, consider whether RLS should be disabled:

drop policy "allow_all" on public.posts;
alter table public.posts disable row level security;

Note: Only disable RLS if you're certain the table should be fully accessible.

Example

Given this problematic configuration:

create table public.user_data(
    id uuid primary key,
    user_id uuid references auth.users(id),
    sensitive_info text
);

alter table public.user_data enable row level security;

-- This policy defeats the purpose of RLS!
create policy "allow_all_select"
on public.user_data
for select
to authenticated
using (true);

The allow_all_select policy allows ANY authenticated user to read ALL rows, including other users' sensitive information.

Fix by adding a proper condition:

drop policy "allow_all_select" on public.user_data;

create policy "users_own_data"
on public.user_data
for select
to authenticated
using (auth.uid() = user_id);

False Positives

In some cases, USING (true) may be intentional:

  • Public read-only tables (e.g., blog posts, product catalogs)
  • Tables where access is controlled by other means (e.g., API layer)

If the policy is intentional, you can document why in a comment or consider suppressing this lint for specific tables.