# 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](/content/dashboard/project/_/database/security-advisor/index.html) and [Performance Advisor](/content/dashboard/project/_/database/performance-advisor/index.html) 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:
```sql
-- 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:
```sql
-- 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:
```sql
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:
```sql
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:
```sql
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.
