SupabaseHigh

Unprotected RPC Function

PostgreSQL functions exposed via the Supabase RPC endpoint can be called without authentication or with insufficient authorization checks.

Last updated 2026-01-15

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 DEFINER ignore 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 Scan