What Is This Vulnerability
Supabase exposes PostgreSQL functions as RPC endpoints via PostgREST. Any function in the public schema can be called through the REST API at /rest/v1/rpc/function_name. If these functions do not verify the caller's identity or permissions, they can be invoked by anyone with the anonymous key.
This is particularly dangerous because RPC functions often contain business logic that bypasses RLS. Functions executed with SECURITY DEFINER run with the privileges of the function owner (typically the database superuser), meaning they can read and write any data regardless of RLS policies.
-- VULNERABLE: This function bypasses RLS and has no auth check
CREATE OR REPLACE FUNCTION get_all_user_emails()
RETURNS TABLE(email text) AS $$
SELECT email FROM auth.users;
$$ LANGUAGE sql SECURITY DEFINER;
An attacker can call this function directly:
curl -X POST 'https://YOUR_PROJECT.supabase.co/rest/v1/rpc/get_all_user_emails' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
Why It's Dangerous
Unprotected RPC functions represent a severe risk because they:
- Bypass RLS entirely: Functions with
SECURITY DEFINERignore all row-level policies - Expose sensitive data: Functions that query internal tables (e.g.,
auth.users) leak data not accessible through normal API calls - Enable privilege escalation: Functions that modify roles, permissions, or credits can be abused
- Execute arbitrary logic: Complex functions may have side effects like sending emails, triggering webhooks, or modifying multiple tables
- Circumvent rate limits: Direct RPC calls may not be subject to the same rate limiting as standard REST queries
How to Detect
List all public functions and check their security characteristics:
SELECT routine_name, routine_type, security_type
FROM information_schema.routines
WHERE routine_schema = 'public'
AND routine_type = 'FUNCTION';
Functions with security_type = 'DEFINER' are especially dangerous if they lack internal auth checks. AuditYourApp calls every discovered RPC function as an anonymous user and reports which ones return data or execute successfully.
Test manually:
const { data, error } = await supabase.rpc('function_name', { param: 'value' });
if (!error) console.log('RPC function is callable without auth');
How to Fix
Add authentication checks inside every public function:
CREATE OR REPLACE FUNCTION get_my_data()
RETURNS TABLE(id uuid, data jsonb) AS $$
BEGIN
-- Verify the caller is authenticated
IF auth.uid() IS NULL THEN
RAISE EXCEPTION 'Not authenticated';
END IF;
RETURN QUERY
SELECT d.id, d.data
FROM user_data d
WHERE d.user_id = auth.uid();
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
For functions that should not be publicly accessible, move them to a private schema:
-- Move function to a non-public schema
CREATE SCHEMA IF NOT EXISTS private;
CREATE OR REPLACE FUNCTION private.internal_function()
RETURNS void AS $$
-- This function is not accessible via the API
$$ LANGUAGE plpgsql;
Prefer SECURITY INVOKER (the default) over SECURITY DEFINER unless the function genuinely needs elevated privileges. Revoke execute permissions from the anonymous role on sensitive functions:
REVOKE EXECUTE ON FUNCTION sensitive_function FROM anon;
REVOKE EXECUTE ON FUNCTION sensitive_function FROM authenticated;
-- Only service_role can call this function
Scan your app for this vulnerability
AuditYourApp automatically detects security misconfigurations in Supabase and Firebase projects. Get actionable remediation in minutes.
Run Free ScanRelated
vulnerabilities
Edge Function Security Issue
Supabase Edge Functions lacking proper authentication checks, input validation, or error handling expose backend logic to abuse.
vulnerabilities
Missing Row Level Security Policy
Tables without RLS are fully exposed to any user with the anon key, allowing unrestricted read and write access to all rows.