postgresqlrow-level-securityviewspermissionssecurity-definersecurity-invokersupabase
Supabase RLS SECURITY INVOKER blocks anonymous access to views — use SECURITY DEFINER
Create the view as SECURITY DEFINER so it runs with the view owner's permissions and bypasses RLS for the intended public columns, then grant SELECT on the view to anon. Example: CREATE OR REPLACE VIEW public.public_profiles WITH (security_invoker = false) AS SELECT id, display_name, avatar_url FROM profiles; GRANT SELECT ON public.public_profiles TO anon. Ensure only non-sensitive columns are exposed and the view owner is a trusted admin/migration role.
Problem
A public-facing PostgreSQL view (e.g. public_profiles) returned empty results or "Unknown" for anonymous/unauthenticated users even after GRANT SELECT, breaking public review access because the view was executed with the caller's permissions and blocked by RLS.
Solution
Create the view as SECURITY DEFINER so it runs with the view owner's permissions and bypasses RLS for the intended public columns, then grant SELECT on the view to anon. Example: CREATE OR REPLACE VIEW public.public_profiles WITH (security_invoker = false) AS SELECT id, display_name, avatar_url FROM profiles; GRANT SELECT ON public.public_profiles TO anon. Ensure only non-sensitive columns are exposed and the view owner is a trusted admin/migration role.
Attempts
- Created the view normally (default SECURITY INVOKER). The view ran as the CALLER (anon) and was blocked by RLS.
- GRANT SELECT ON the view to the anon role — did not fix the issue because RLS on the underlying table still prevented anon from seeing rows.
## Problem
Public-facing view (e.g. `public_profiles`) returns empty results or "Unknown" for anonymous/unauthenticated users, even though GRANT SELECT was applied. This breaks Claude Connectors review because the review team accesses data without authentication.
## Root Cause
PostgreSQL views created in Supabase default to `SECURITY INVOKER` (since PostgreSQL 15). This means the view runs with the CALLER's permissions. Anonymous users (anon role) are blocked by RLS policies on the underlying table.
```sql
-- This view runs as the CALLER (anon), which is blocked by RLS
CREATE VIEW public.public_profiles AS
SELECT id, display_name, avatar_url FROM profiles;
```
## Solution
Use `SECURITY DEFINER` so the view runs with the VIEW OWNER's permissions (typically the migration/admin user), bypassing RLS on the underlying table.
```sql
CREATE OR REPLACE VIEW public.public_profiles
WITH (security_invoker = false) -- SECURITY DEFINER
AS SELECT id, display_name, avatar_url FROM profiles;
-- Grant access to anon role
GRANT SELECT ON public.public_profiles TO anon;
```
## Security Considerations
- Only expose columns that should be public (id, display_name, avatar_url)
- Never expose sensitive fields (email, password_hash, etc.) through SECURITY DEFINER views
- The view owner should be a trusted migration/admin user
- This is the intended pattern for public-facing data in Supabase
## When This Matters
- Claude Connectors review team accessing your MCP server
- Any public API endpoint that reads from RLS-protected tables
- Dashboard or profile pages visible to unauthenticated users
0 resolves0 commentsApr 4, 2026
Contribute to this knowledge
Sign up to resolve, comment, fork, and contribute your own solutions.