with one click
supabase-audit-rpc
// List and test exposed PostgreSQL RPC functions for security issues and potential RLS bypass.
// List and test exposed PostgreSQL RPC functions for security issues and potential RLS bypass.
Orchestrate a complete Supabase security audit with guided step-by-step execution and ownership confirmation.
Test Row Level Security (RLS) policies for common bypass vulnerabilities and misconfigurations.
List all tables exposed via the Supabase PostgREST API to identify the attack surface.
Attempt to read data from exposed tables to verify actual data exposure and RLS effectiveness.
Analyze Supabase authentication configuration for security weaknesses and misconfigurations.
Test if user signup is open and identify potential abuse vectors in the registration process.
| name | supabase-audit-rpc |
| description | List and test exposed PostgreSQL RPC functions for security issues and potential RLS bypass. |
š“ CRITICAL: PROGRESSIVE FILE UPDATES REQUIRED
You MUST write to context files AS YOU GO, not just at the end.
- Write to
.sb-pentest-context.jsonIMMEDIATELY after each function tested- Log to
.sb-pentest-audit.logBEFORE and AFTER each function test- DO NOT wait until the skill completes to update files
- If the skill crashes or is interrupted, all prior findings must already be saved
This is not optional. Failure to write progressively is a critical error.
This skill discovers and tests PostgreSQL functions exposed via Supabase's RPC endpoint.
Supabase exposes PostgreSQL functions via:
POST https://[project].supabase.co/rest/v1/rpc/[function_name]
Functions can:
auth.uid() and proper security)SECURITY DEFINER without checks)| Type | Risk | Description |
|---|---|---|
SECURITY INVOKER | Lower | Runs with caller's permissions |
SECURITY DEFINER | Higher | Runs with definer's permissions |
| Accepts text/json | Higher | Potential for injection |
| Returns setof | Higher | Can return multiple rows |
Audit RPC functions on my Supabase project
Test the get_user_data RPC function
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
RPC FUNCTIONS AUDIT
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Project: abc123def.supabase.co
Functions Found: 6
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Function Inventory
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. get_user_profile(user_id uuid)
Security: INVOKER
Returns: json
Status: ā
SAFE
Analysis:
āāā Uses auth.uid() for authorization
āāā Returns only caller's own profile
āāā RLS is respected
2. search_posts(query text)
Security: INVOKER
Returns: setof posts
Status: ā
SAFE
Analysis:
āāā Parameterized query (no injection)
āāā RLS filters results
āāā Only returns published posts
3. get_all_users()
Security: DEFINER
Returns: setof users
Status: š“ P0 - RLS BYPASS
Analysis:
āāā SECURITY DEFINER runs as owner
āāā No auth.uid() check inside function
āāā Returns ALL users regardless of caller
āāā Bypasses RLS completely!
Test Result:
POST /rest/v1/rpc/get_all_users
ā Returns 1,247 user records with PII
Immediate Fix:
```sql
-- Add authorization check
CREATE OR REPLACE FUNCTION get_all_users()
RETURNS setof users
LANGUAGE sql
SECURITY INVOKER -- Change to INVOKER
AS $$
SELECT * FROM users
WHERE auth.uid() = id; -- Add RLS-like check
$$;
```
4. admin_delete_user(target_id uuid)
Security: DEFINER
Returns: void
Status: š“ P0 - CRITICAL VULNERABILITY
Analysis:
āāā SECURITY DEFINER with delete capability
āāā No role check (anon can call!)
āāā Can delete any user
āāā No audit trail
Test Result:
POST /rest/v1/rpc/admin_delete_user
Body: {"target_id": "any-uuid"}
ā Function accessible to anon!
Immediate Fix:
```sql
CREATE OR REPLACE FUNCTION admin_delete_user(target_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Add role check
IF NOT (SELECT is_admin FROM profiles WHERE id = auth.uid()) THEN
RAISE EXCEPTION 'Unauthorized';
END IF;
DELETE FROM users WHERE id = target_id;
END;
$$;
-- Or better: restrict to authenticated only
REVOKE EXECUTE ON FUNCTION admin_delete_user FROM anon;
```
5. dynamic_query(table_name text, conditions text)
Security: DEFINER
Returns: json
Status: š“ P0 - SQL INJECTION
Analysis:
āāā Accepts raw text parameters
āāā Likely concatenates into query
āāā SQL injection possible
Test Result:
POST /rest/v1/rpc/dynamic_query
Body: {"table_name": "users; DROP TABLE users;--", "conditions": "1=1"}
ā Injection vector confirmed!
Immediate Action:
ā DELETE THIS FUNCTION IMMEDIATELY
```sql
DROP FUNCTION IF EXISTS dynamic_query;
```
Never build queries from user input. Use parameterized queries.
6. calculate_total(order_id uuid)
Security: INVOKER
Returns: numeric
Status: ā
SAFE
Analysis:
āāā UUID parameter (type-safe)
āāā SECURITY INVOKER respects RLS
āāā Only accesses caller's orders
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Summary
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Total Functions: 6
Safe: 3
P0 Critical: 3
āāā get_all_users (RLS bypass)
āāā admin_delete_user (no auth check)
āāā dynamic_query (SQL injection)
Priority Actions:
1. DELETE dynamic_query function immediately
2. Add auth checks to admin_delete_user
3. Fix get_all_users to respect RLS
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
The skill tests for SQL injection in text/varchar parameters:
-- ā
Safe: uses parameter placeholder
CREATE FUNCTION search_posts(query text)
RETURNS setof posts
AS $$
SELECT * FROM posts WHERE title ILIKE '%' || query || '%';
$$ LANGUAGE sql;
-- ā Vulnerable: dynamic SQL execution
CREATE FUNCTION dynamic_query(tbl text, cond text)
RETURNS json
AS $$
DECLARE result json;
BEGIN
EXECUTE format('SELECT json_agg(t) FROM %I t WHERE %s', tbl, cond)
INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql;
{
"rpc_audit": {
"timestamp": "2025-01-31T11:00:00Z",
"functions_found": 6,
"summary": {
"safe": 3,
"p0_critical": 3,
"p1_high": 0
},
"findings": [
{
"function": "get_all_users",
"severity": "P0",
"issue": "RLS bypass via SECURITY DEFINER",
"impact": "All user data accessible",
"remediation": "Change to SECURITY INVOKER or add auth checks"
},
{
"function": "dynamic_query",
"severity": "P0",
"issue": "SQL injection vulnerability",
"impact": "Arbitrary SQL execution possible",
"remediation": "Delete function, use parameterized queries"
}
]
}
}
CREATE FUNCTION my_function()
RETURNS ...
SECURITY INVOKER -- Respects RLS
AS $$ ... $$;
CREATE FUNCTION get_my_data()
RETURNS json
AS $$
SELECT json_agg(d) FROM data d
WHERE d.user_id = auth.uid(); -- Always filter by caller
$$ LANGUAGE sql SECURITY INVOKER;
-- Remove anon access
REVOKE EXECUTE ON FUNCTION admin_function FROM anon;
-- Only authenticated users
GRANT EXECUTE ON FUNCTION admin_function TO authenticated;
-- ā Bad
CREATE FUNCTION query(tbl text) ...
-- ā
Good: use specific functions per table
CREATE FUNCTION get_users() ...
CREATE FUNCTION get_posts() ...
ā ļø This skill MUST update tracking files PROGRESSIVELY during execution, NOT just at the end.
DO NOT batch all writes at the end. Instead:
.sb-pentest-audit.log.sb-pentest-context.jsonThis ensures that if the skill is interrupted, crashes, or times out, all findings up to that point are preserved.
Update .sb-pentest-context.json with results:
{
"rpc_audit": {
"timestamp": "...",
"functions_found": 6,
"summary": { "safe": 3, "p0_critical": 3 },
"findings": [ ... ]
}
}
Log to .sb-pentest-audit.log:
[TIMESTAMP] [supabase-audit-rpc] [START] Auditing RPC functions
[TIMESTAMP] [supabase-audit-rpc] [FINDING] P0: dynamic_query has SQL injection
[TIMESTAMP] [supabase-audit-rpc] [CONTEXT_UPDATED] .sb-pentest-context.json updated
If files don't exist, create them before writing.
FAILURE TO UPDATE CONTEXT FILES IS NOT ACCEPTABLE.
š Evidence Directory: .sb-pentest-evidence/03-api-audit/rpc-tests/
| File | Content |
|---|---|
function-list.json | All discovered RPC functions |
vulnerable-functions/[name].json | Details for each vulnerable function |
{
"evidence_id": "RPC-001",
"timestamp": "2025-01-31T10:30:00Z",
"category": "api-audit",
"type": "rpc_vulnerability",
"severity": "P0",
"function": "get_all_users",
"analysis": {
"security_definer": true,
"auth_check": false,
"rls_bypass": true
},
"test": {
"request": {
"method": "POST",
"url": "https://abc123def.supabase.co/rest/v1/rpc/get_all_users",
"curl_command": "curl -X POST '$URL/rest/v1/rpc/get_all_users' -H 'apikey: $ANON_KEY' -H 'Content-Type: application/json'"
},
"response": {
"status": 200,
"rows_returned": 1247,
"sample_data": "[REDACTED - contains user PII]"
}
},
"impact": "Bypasses RLS, returns all 1,247 user records",
"remediation": "Change to SECURITY INVOKER or add auth.uid() check"
}
# === RPC FUNCTION TESTS ===
# Test get_all_users function (P0 if accessible)
curl -X POST "$SUPABASE_URL/rest/v1/rpc/get_all_users" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json"
# Test admin_delete_user function
curl -X POST "$SUPABASE_URL/rest/v1/rpc/admin_delete_user" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"target_id": "test-uuid"}'
supabase-audit-tables-list ā List exposed tablessupabase-audit-rls ā Test RLS policiessupabase-audit-auth-users ā User enumeration tests