supabase-audit-rpc by yoanbernabeu/supabase-pentest-skills
npx skills add https://github.com/yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rpc🔴 严重:需要渐进式文件更新
你必须在测试过程中就写入上下文文件,而不是等到最后。
- 测试完每个函数后立即写入
.sb-pentest-context.json- 测试每个函数前后都要记录到
.sb-pentest-audit.log- 不要等到技能完成才更新文件
- 如果技能崩溃或中断,所有之前的发现必须已经保存
这不是可选的。不进行渐进式写入是严重错误。
此技能用于发现和测试通过 Supabase 的 RPC 端点暴露的 PostgreSQL 函数。
Supabase 通过以下方式暴露 PostgreSQL 函数:
POST https://[project].supabase.co/rest/v1/rpc/[function_name]
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
函数可以:
auth.uid() 和适当的安全措施)SECURITY DEFINER 没有检查)| 类型 | 风险 | 描述 |
|---|---|---|
SECURITY INVOKER | 较低 | 以调用者权限运行 |
SECURITY DEFINER | 较高 | 以定义者权限运行 |
| 接受 text/json 参数 | 较高 | 可能存在注入风险 |
| 返回 setof | 较高 | 可以返回多行数据 |
审计我的 Supabase 项目上的 RPC 函数
测试 get_user_data RPC 函数
═══════════════════════════════════════════════════════════
RPC 函数审计
═══════════════════════════════════════════════════════════
项目:abc123def.supabase.co
发现的函数:6
─────────────────────────────────────────────────────────
函数清单
─────────────────────────────────────────────────────────
1. get_user_profile(user_id uuid)
安全类型:INVOKER
返回类型:json
状态:✅ 安全
分析:
├── 使用 auth.uid() 进行授权
├── 仅返回调用者自己的个人资料
└── 遵守 RLS
2. search_posts(query text)
安全类型:INVOKER
返回类型:setof posts
状态:✅ 安全
分析:
├── 参数化查询(无注入)
├── RLS 过滤结果
└── 仅返回已发布的帖子
3. get_all_users()
安全类型:DEFINER
返回类型:setof users
状态:🔴 P0 - RLS 绕过
分析:
├── SECURITY DEFINER 以所有者身份运行
├── 函数内部无 auth.uid() 检查
├── 无论调用者是谁都返回所有用户
└── 完全绕过 RLS!
测试结果:
POST /rest/v1/rpc/get_all_users
→ 返回 1,247 条包含 PII 的用户记录
立即修复:
```sql
-- 添加授权检查
CREATE OR REPLACE FUNCTION get_all_users()
RETURNS setof users
LANGUAGE sql
SECURITY INVOKER -- 改为 INVOKER
AS $$
SELECT * FROM users
WHERE auth.uid() = id; -- 添加类似 RLS 的检查
$$;
```
4. admin_delete_user(target_id uuid)
安全类型:DEFINER
返回类型:void
状态:🔴 P0 - 严重漏洞
分析:
├── 具有删除能力的 SECURITY DEFINER
├── 无角色检查(匿名用户可调用!)
├── 可以删除任何用户
└── 无审计追踪
测试结果:
POST /rest/v1/rpc/admin_delete_user
请求体:{"target_id": "any-uuid"}
→ 函数对匿名用户可访问!
立即修复:
```sql
CREATE OR REPLACE FUNCTION admin_delete_user(target_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- 添加角色检查
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;
$$;
-- 或者更好:限制为仅认证用户
REVOKE EXECUTE ON FUNCTION admin_delete_user FROM anon;
```
5. dynamic_query(table_name text, conditions text)
安全类型:DEFINER
返回类型:json
状态:🔴 P0 - SQL 注入
分析:
├── 接受原始文本参数
├── 可能拼接进查询
├── 可能存在 SQL 注入
测试结果:
POST /rest/v1/rpc/dynamic_query
请求体:{"table_name": "users; DROP TABLE users;--", "conditions": "1=1"}
→ 确认存在注入向量!
立即行动:
→ 立即删除此函数
```sql
DROP FUNCTION IF EXISTS dynamic_query;
```
切勿从用户输入构建查询。使用参数化查询。
6. calculate_total(order_id uuid)
安全类型:INVOKER
返回类型:numeric
状态:✅ 安全
分析:
├── UUID 参数(类型安全)
├── SECURITY INVOKER 遵守 RLS
└── 仅访问调用者的订单
─────────────────────────────────────────────────────────
总结
─────────────────────────────────────────────────────────
函数总数:6
安全:3
P0 严重:3
├── get_all_users (RLS 绕过)
├── admin_delete_user (无授权检查)
└── dynamic_query (SQL 注入)
优先行动:
1. 立即删除 dynamic_query 函数
2. 为 admin_delete_user 添加授权检查
3. 修复 get_all_users 以遵守 RLS
═══════════════════════════════════════════════════════════
此技能测试 text/varchar 参数中的 SQL 注入:
-- ✅ 安全:使用参数占位符
CREATE FUNCTION search_posts(query text)
RETURNS setof posts
AS $$
SELECT * FROM posts WHERE title ILIKE '%' || query || '%';
$$ LANGUAGE sql;
-- ❌ 易受攻击:动态 SQL 执行
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": "通过 SECURITY DEFINER 绕过 RLS",
"impact": "所有用户数据可访问",
"remediation": "改为 SECURITY INVOKER 或添加授权检查"
},
{
"function": "dynamic_query",
"severity": "P0",
"issue": "SQL 注入漏洞",
"impact": "可能执行任意 SQL",
"remediation": "删除函数,使用参数化查询"
}
]
}
}
CREATE FUNCTION my_function()
RETURNS ...
SECURITY INVOKER -- 遵守 RLS
AS $$ ... $$;
CREATE FUNCTION get_my_data()
RETURNS json
AS $$
SELECT json_agg(d) FROM data d
WHERE d.user_id = auth.uid(); -- 始终按调用者过滤
$$ LANGUAGE sql SECURITY INVOKER;
-- 移除匿名访问
REVOKE EXECUTE ON FUNCTION admin_function FROM anon;
-- 仅限认证用户
GRANT EXECUTE ON FUNCTION admin_function TO authenticated;
-- ❌ 不好
CREATE FUNCTION query(tbl text) ...
-- ✅ 好:为每个表使用特定函数
CREATE FUNCTION get_users() ...
CREATE FUNCTION get_posts() ...
⚠️ 此技能必须在执行过程中渐进式更新跟踪文件,而不是仅在最后更新。
不要在最后批量写入所有内容。而是:
.sb-pentest-audit.log.sb-pentest-context.json这确保了如果技能被中断、崩溃或超时,所有到该点为止的发现都会被保存。
更新.sb-pentest-context.json 包含结果:
{ "rpc_audit": { "timestamp": "...", "functions_found": 6, "summary": { "safe": 3, "p0_critical": 3 }, "findings": [ ... ] } }
记录到.sb-pentest-audit.log:
[TIMESTAMP] [supabase-audit-rpc] [START] 审计 RPC 函数 [TIMESTAMP] [supabase-audit-rpc] [FINDING] P0: dynamic_query 存在 SQL 注入 [TIMESTAMP] [supabase-audit-rpc] [CONTEXT_UPDATED] .sb-pentest-context.json 已更新
如果文件不存在,在写入前创建它们。
未能更新上下文文件是不可接受的。
📁 证据目录: .sb-pentest-evidence/03-api-audit/rpc-tests/
| 文件 | 内容 |
|---|---|
function-list.json | 所有发现的 RPC 函数 |
vulnerable-functions/[name].json | 每个易受攻击函数的详细信息 |
{
"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 - 包含用户 PII]"
}
},
"impact": "绕过 RLS,返回所有 1,247 条用户记录",
"remediation": "改为 SECURITY INVOKER 或添加 auth.uid() 检查"
}
# === RPC 函数测试 ===
# 测试 get_all_users 函数(如果可访问则为 P0)
curl -X POST "$SUPABASE_URL/rest/v1/rpc/get_all_users" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json"
# 测试 admin_delete_user 函数
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 — 列出暴露的表supabase-audit-rls — 测试 RLS 策略supabase-audit-auth-users — 用户枚举测试每周安装次数
98
仓库
GitHub 星标数
33
首次出现
2026年1月31日
安全审计
安装于
claude-code82
codex73
opencode72
gemini-cli69
cursor64
github-copilot64
🔴 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 testsWeekly Installs
98
Repository
GitHub Stars
33
First Seen
Jan 31, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
claude-code82
codex73
opencode72
gemini-cli69
cursor64
github-copilot64
Azure PostgreSQL 无密码身份验证配置指南:Entra ID 迁移与访问管理
34,800 周安装