supabase-audit-realtime by yoanbernabeu/supabase-pentest-skills
npx skills add https://github.com/yoanbernabeu/supabase-pentest-skills --skill supabase-audit-realtime🔴 关键:需要渐进式文件更新
你必须边执行边写入上下文文件,而不是等到最后才写。
- 测试完每个频道后立即写入
.sb-pentest-context.json- 在每个订阅测试前后记录到
.sb-pentest-audit.log- 切勿等到技能完成后再更新文件
- 如果技能崩溃或被中断,所有之前的发现必须已经保存
这不是可选的。未能渐进式写入是关键错误。
此技能用于测试 Supabase 实时 WebSocket 频道是否存在安全问题。
Supabase 实时功能支持:
wss://[project].supabase.co/realtime/v1/websocket
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 功能 | 描述 |
|---|---|
| Postgres 变更 | 流式传输数据库变更 |
| 广播 | 发布/订阅消息传递 |
| 在线状态 | 用户在线状态跟踪 |
实时功能遵循 RLS 策略:
| 测试 | 目的 |
|---|---|
| 频道枚举 | 查找开放频道 |
| Postgres 变更 | 测试表流式传输 |
| 广播 | 测试发布/订阅访问 |
| 在线状态 | 测试在线状态频道访问 |
审计我的 Supabase 项目上的实时频道
测试 Postgres 变更是否流式传输敏感数据
═══════════════════════════════════════════════════════════
实时频道审计
═══════════════════════════════════════════════════════════
项目:abc123def.supabase.co
端点:wss://abc123def.supabase.co/realtime/v1/websocket
─────────────────────────────────────────────────────────
连接测试
─────────────────────────────────────────────────────────
WebSocket 连接:✅ 已建立
身份验证:匿名密钥已接受
协议:Phoenix 频道
─────────────────────────────────────────────────────────
Postgres 变更测试
─────────────────────────────────────────────────────────
使用匿名密钥订阅表变更...
表:users
├── 订阅:✅ 已订阅
├── INSERT 事件:🔴 P0 - 接收所有新用户
├── UPDATE 事件:🔴 P0 - 接收所有更新
└── DELETE 事件:🔴 P0 - 接收所有删除
接收到的示例事件:
```json
{
"type": "INSERT",
"table": "users",
"record": {
"id": "550e8400-e29b-...",
"email": "newuser@example.com", ← PII 正在流式传输!
"name": "New User",
"created_at": "2025-01-31T10:00:00Z"
}
}
发现:🔴 P0 - 用户数据未经身份验证即流式传输!RLS 可能未为实时功能正确配置。
表:orders ├── 订阅:✅ 已订阅 ├── INSERT 事件:❌ 未接收(RLS 正常工作) ├── UPDATE 事件:❌ 未接收(RLS 正常工作) └── DELETE 事件:❌ 未接收(RLS 正常工作)
评估:✅ orders 表得到适当保护。
表:posts ├── 订阅:✅ 已订阅 ├── INSERT 事件:✅ 仅接收已发布的 ├── UPDATE 事件:✅ 仅接收已发布的 └── DELETE 事件:✅ 仅接收已发布的
评估:✅ posts 流式传输遵循 RLS(仅已发布的)。
───────────────────────────────────────────────────────── 广播频道测试 ─────────────────────────────────────────────────────────
尝试订阅常见频道名称...
频道:room:lobby ├── 订阅:✅ 成功 ├── 消息:接收广播 └── 评估:ℹ️ 开放频道(可能是有意为之)
频道:admin ├── 订阅:✅ 成功 ← 这应该是公开的吗? ├── 消息:接收管理员通知 └── 评估:🟠 P1 - 管理员频道可公开访问
频道:notifications ├── 订阅:✅ 成功 ├── 消息:接收所有用户的通知! └── 评估:🔴 P0 - 用户通知暴露
示例通知:
{
"user_id": "123...",
"type": "payment_received",
"amount": 150.00,
"from": "customer@example.com"
}
───────────────────────────────────────────────────────── 在线状态测试 ─────────────────────────────────────────────────────────
频道:online-users ├── 订阅:✅ 成功 ├── 在线状态列表:接收所有在线用户 └── 在线用户:47
示例在线状态数据:
{
"user_id": "550e8400-...",
"email": "user@example.com",
"status": "online",
"last_seen": "2025-01-31T14:00:00Z"
}
评估:🟠 P1 - 用户在线状态数据暴露 请考虑电子邮件/用户ID是否应可见。
───────────────────────────────────────────────────────── 总结 ─────────────────────────────────────────────────────────
Postgres 变更: ├── 🔴 P0:users 表流式传输所有数据 ├── ✅ 通过:orders 表受 RLS 保护 └── ✅ 通过:posts 表正确过滤
广播: ├── 🔴 P0:notifications 频道暴露用户数据 ├── 🟠 P1:admin 频道可公开访问 └── ℹ️ 信息:lobby 频道开放(检查是否为有意为之)
在线状态: └── 🟠 P1:online-users 暴露用户详细信息
关键发现:2 个 高级别发现:2 个
═══════════════════════════════════════════════════════════ 建议 ═══════════════════════════════════════════════════════════
修复 USERS 表 RLS 确保 RLS 适用于实时功能:
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see only themselves"
ON users FOR SELECT
USING (auth.uid() = id);
保护广播频道 使用实时授权:
// 对敏感频道要求身份验证
const channel = supabase.channel('admin', {
config: {
broadcast: { ack: true },
presence: { key: userId }
}
})
// 服务器端:验证频道访问权限
// 在 realtime.channels 表上使用 RLS
限制在线状态数据 仅共享必要信息:
channel.track({
online_at: new Date().toISOString()
// 除非需要,否则不要包含电子邮件、用户ID
})
═══════════════════════════════════════════════════════════
## 实时安全模型
### Postgres 变更 + RLS
```sql
-- 此 RLS 策略也适用于实时功能
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- 使用此策略:
-- - API SELECT:仅自己的数据
-- - 实时:仅自己数据的变更
-- 实时授权(Supabase 扩展)
-- 向 realtime.channels 虚拟表添加策略
-- 仅认证用户可以加入
CREATE POLICY "Authenticated users join channels"
ON realtime.channels FOR SELECT
USING (auth.role() = 'authenticated');
-- 或限制特定频道
CREATE POLICY "Admin channel for admins"
ON realtime.channels FOR SELECT
USING (
name != 'admin' OR
(SELECT is_admin FROM profiles WHERE id = auth.uid())
);
{
"realtime_audit": {
"timestamp": "2025-01-31T14:00:00Z",
"connection": "established",
"postgres_changes": {
"users": {
"subscribed": true,
"receiving_events": true,
"severity": "P0",
"finding": "All user data streaming without RLS"
},
"orders": {
"subscribed": true,
"receiving_events": false,
"severity": null,
"finding": "Properly protected by RLS"
}
},
"broadcast": {
"notifications": {
"accessible": true,
"severity": "P0",
"finding": "User notifications exposed"
},
"admin": {
"accessible": true,
"severity": "P1",
"finding": "Admin channel publicly accessible"
}
},
"presence": {
"online-users": {
"accessible": true,
"severity": "P1",
"users_visible": 47,
"finding": "User presence data exposed"
}
}
}
}
| 问题 | 原因 | 修复方法 |
|---|---|---|
| 所有数据流式传输 | RLS 未启用/配置 | 启用并配置 RLS |
| 广播开放 | 无频道授权 | 添加频道策略 |
| 在线状态暴露 | 跟踪数据过多 | 最小化跟踪数据 |
-- 确保 RLS 已启用
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- 仅限认证用户的策略
CREATE POLICY "Users see own profile" ON users
FOR SELECT
USING (auth.uid() = id);
-- 实时功能现在将仅流式传输认证用户行的变更
// 客户端:订阅前检查访问权限
const { data: canAccess } = await supabase
.from('channel_access')
.select('*')
.eq('channel', 'admin')
.eq('user_id', userId)
.single();
if (canAccess) {
const channel = supabase.channel('admin');
channel.subscribe();
}
// 之前(数据过多)
channel.track({
user_id: userId,
email: email,
name: fullName,
avatar: avatarUrl
});
// 之后(最小化数据)
channel.track({
online_at: new Date().toISOString()
// 如果需要,单独获取用户详细信息
});
⚠️ 此技能必须在执行过程中渐进式更新跟踪文件,而不是仅在最后更新。
切勿在最后批量写入所有内容。而是:
.sb-pentest-audit.log.sb-pentest-context.json这确保了如果技能被中断、崩溃或超时,所有到该点为止的发现都会被保留。
使用结果更新 .sb-pentest-context.json:
{
"realtime_audit": {
"timestamp": "...",
"connection": "established",
"postgres_changes": { ... },
"broadcast": { ... },
"presence": { ... }
}
}
记录到 .sb-pentest-audit.log:
[TIMESTAMP] [supabase-audit-realtime] [START] 审计实时频道
[TIMESTAMP] [supabase-audit-realtime] [FINDING] P0:users 表流式传输所有数据
[TIMESTAMP] [supabase-audit-realtime] [CONTEXT_UPDATED] .sb-pentest-context.json 已更新
如果文件不存在,在写入前创建它们。
未能更新上下文文件是不可接受的。
📁 证据目录: .sb-pentest-evidence/06-realtime-audit/
| 文件 | 内容 |
|---|---|
websocket-connection.json | WebSocket 连接测试 |
postgres-changes/[table].json | 表订阅结果 |
broadcast-channels/[channel].json | 广播频道访问 |
presence-data/[channel].json | 在线状态数据暴露 |
{
"evidence_id": "RT-001",
"timestamp": "2025-01-31T11:05:00Z",
"category": "realtime-audit",
"type": "postgres_changes",
"severity": "P0",
"table": "users",
"subscription_test": {
"channel": "realtime:public:users",
"subscribed": true,
"events_received": true
},
"sample_event": {
"type": "INSERT",
"table": "users",
"record": {
"id": "[REDACTED]",
"email": "[REDACTED]@example.com",
"name": "[REDACTED]"
},
"redacted": true
},
"impact": {
"pii_streaming": true,
"affected_columns": ["email", "name"],
"rls_bypass": true
},
"websocket_url": "wss://abc123def.supabase.co/realtime/v1/websocket",
"reproduction_code": "const channel = supabase.channel('realtime:public:users').on('postgres_changes', { event: '*', schema: 'public', table: 'users' }, (payload) => console.log(payload)).subscribe()"
}
supabase-audit-rls — RLS 影响实时功能supabase-audit-tables-read — API 访问相关supabase-report — 包含在最终报告中每周安装量
96
仓库
GitHub 星标数
32
首次出现
2026年1月31日
安全审计
安装于
claude-code76
codex71
opencode70
gemini-cli66
github-copilot62
cursor60
🔴 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 channel tested- Log to
.sb-pentest-audit.logBEFORE and AFTER each subscription 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 tests Supabase Realtime WebSocket channels for security issues.
Supabase Realtime enables:
wss://[project].supabase.co/realtime/v1/websocket
| Feature | Description |
|---|---|
| Postgres Changes | Stream database changes |
| Broadcast | Pub/sub messaging |
| Presence | User presence tracking |
Realtime respects RLS policies:
| Test | Purpose |
|---|---|
| Channel enumeration | Find open channels |
| Postgres Changes | Test table streaming |
| Broadcast | Test pub/sub access |
| Presence | Test presence channel access |
Audit Realtime channels on my Supabase project
Test if Postgres Changes streams sensitive data
═══════════════════════════════════════════════════════════
REALTIME CHANNEL AUDIT
═══════════════════════════════════════════════════════════
Project: abc123def.supabase.co
Endpoint: wss://abc123def.supabase.co/realtime/v1/websocket
─────────────────────────────────────────────────────────
Connection Test
─────────────────────────────────────────────────────────
WebSocket Connection: ✅ Established
Authentication: Anon key accepted
Protocol: Phoenix channels
─────────────────────────────────────────────────────────
Postgres Changes Test
─────────────────────────────────────────────────────────
Subscribing to table changes with anon key...
Table: users
├── Subscribe: ✅ Subscribed
├── INSERT events: 🔴 P0 - RECEIVING ALL NEW USERS
├── UPDATE events: 🔴 P0 - RECEIVING ALL UPDATES
└── DELETE events: 🔴 P0 - RECEIVING ALL DELETES
Sample Event Received:
```json
{
"type": "INSERT",
"table": "users",
"record": {
"id": "550e8400-e29b-...",
"email": "newuser@example.com", ← PII STREAMING!
"name": "New User",
"created_at": "2025-01-31T10:00:00Z"
}
}
Finding: 🔴 P0 - User data streaming without authentication! RLS may not be properly configured for Realtime.
Table: orders ├── Subscribe: ✅ Subscribed ├── INSERT events: ❌ Not receiving (RLS working) ├── UPDATE events: ❌ Not receiving (RLS working) └── DELETE events: ❌ Not receiving (RLS working)
Assessment: ✅ Orders table properly protected.
Table: posts ├── Subscribe: ✅ Subscribed ├── INSERT events: ✅ Receiving published only ├── UPDATE events: ✅ Receiving published only └── DELETE events: ✅ Receiving published only
Assessment: ✅ Posts streaming respects RLS (published only).
───────────────────────────────────────────────────────── Broadcast Channel Test ─────────────────────────────────────────────────────────
Attempting to subscribe to common channel names...
Channel: room:lobby ├── Subscribe: ✅ Success ├── Messages: Receiving broadcasts └── Assessment: ℹ️ Open channel (may be intentional)
Channel: admin ├── Subscribe: ✅ Success ← Should this be public? ├── Messages: Receiving admin notifications └── Assessment: 🟠 P1 - Admin channel publicly accessible
Channel: notifications ├── Subscribe: ✅ Success ├── Messages: Receiving user notifications for ALL users! └── Assessment: 🔴 P0 - User notifications exposed
Sample Notification:
{
"user_id": "123...",
"type": "payment_received",
"amount": 150.00,
"from": "customer@example.com"
}
───────────────────────────────────────────────────────── Presence Test ─────────────────────────────────────────────────────────
Channel: online-users ├── Subscribe: ✅ Success ├── Presence List: Receiving all online users └── Users Online: 47
Sample Presence Data:
{
"user_id": "550e8400-...",
"email": "user@example.com",
"status": "online",
"last_seen": "2025-01-31T14:00:00Z"
}
Assessment: 🟠 P1 - User presence data exposed Consider if email/user_id should be visible.
───────────────────────────────────────────────────────── Summary ─────────────────────────────────────────────────────────
Postgres Changes: ├── 🔴 P0: users table streaming all data ├── ✅ PASS: orders table protected by RLS └── ✅ PASS: posts table correctly filtered
Broadcast: ├── 🔴 P0: notifications channel exposing user data ├── 🟠 P1: admin channel publicly accessible └── ℹ️ INFO: lobby channel open (review if intended)
Presence: └── 🟠 P1: online-users exposing user details
Critical Findings: 2 High Findings: 2
═══════════════════════════════════════════════════════════ Recommendations ═══════════════════════════════════════════════════════════
FIX USERS TABLE RLS Ensure RLS applies to Realtime:
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see only themselves"
ON users FOR SELECT
USING (auth.uid() = id);
SECURE BROADCAST CHANNELS Use Realtime Authorization:
// Require auth for sensitive channels
const channel = supabase.channel('admin', {
config: {
broadcast: { ack: true },
presence: { key: userId }
}
})
// Server-side: validate channel access
// Use RLS on realtime.channels table
LIMIT PRESENCE DATA Only share necessary information:
channel.track({
online_at: new Date().toISOString()
// Don't include email, user_id unless needed
})
═══════════════════════════════════════════════════════════
## Realtime Security Model
### Postgres Changes + RLS
```sql
-- This RLS policy applies to Realtime too
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- With this policy:
-- - API SELECT: Only own data
-- - Realtime: Only own data changes
-- Realtime authorization (Supabase extension)
-- Add policies to realtime.channels virtual table
-- Only authenticated users can join
CREATE POLICY "Authenticated users join channels"
ON realtime.channels FOR SELECT
USING (auth.role() = 'authenticated');
-- Or restrict specific channels
CREATE POLICY "Admin channel for admins"
ON realtime.channels FOR SELECT
USING (
name != 'admin' OR
(SELECT is_admin FROM profiles WHERE id = auth.uid())
);
{
"realtime_audit": {
"timestamp": "2025-01-31T14:00:00Z",
"connection": "established",
"postgres_changes": {
"users": {
"subscribed": true,
"receiving_events": true,
"severity": "P0",
"finding": "All user data streaming without RLS"
},
"orders": {
"subscribed": true,
"receiving_events": false,
"severity": null,
"finding": "Properly protected by RLS"
}
},
"broadcast": {
"notifications": {
"accessible": true,
"severity": "P0",
"finding": "User notifications exposed"
},
"admin": {
"accessible": true,
"severity": "P1",
"finding": "Admin channel publicly accessible"
}
},
"presence": {
"online-users": {
"accessible": true,
"severity": "P1",
"users_visible": 47,
"finding": "User presence data exposed"
}
}
}
}
| Issue | Cause | Fix |
|---|---|---|
| All data streaming | RLS not enabled/configured | Enable and configure RLS |
| Broadcast open | No channel authorization | Add channel policies |
| Presence exposed | Too much data tracked | Minimize tracked data |
-- Ensure RLS is enabled
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Policy for authenticated users only
CREATE POLICY "Users see own profile" ON users
FOR SELECT
USING (auth.uid() = id);
-- Realtime will now only stream changes for the authenticated user's row
// Client: Check access before subscribing
const { data: canAccess } = await supabase
.from('channel_access')
.select('*')
.eq('channel', 'admin')
.eq('user_id', userId)
.single();
if (canAccess) {
const channel = supabase.channel('admin');
channel.subscribe();
}
// Before (too much data)
channel.track({
user_id: userId,
email: email,
name: fullName,
avatar: avatarUrl
});
// After (minimal data)
channel.track({
online_at: new Date().toISOString()
// User details fetched separately if needed
});
⚠️ 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:
{
"realtime_audit": {
"timestamp": "...",
"connection": "established",
"postgres_changes": { ... },
"broadcast": { ... },
"presence": { ... }
}
}
Log to.sb-pentest-audit.log:
[TIMESTAMP] [supabase-audit-realtime] [START] Auditing Realtime channels
[TIMESTAMP] [supabase-audit-realtime] [FINDING] P0: users table streaming all data
[TIMESTAMP] [supabase-audit-realtime] [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/06-realtime-audit/
| File | Content |
|---|---|
websocket-connection.json | WebSocket connection test |
postgres-changes/[table].json | Table subscription results |
broadcast-channels/[channel].json | Broadcast channel access |
presence-data/[channel].json | Presence data exposure |
{
"evidence_id": "RT-001",
"timestamp": "2025-01-31T11:05:00Z",
"category": "realtime-audit",
"type": "postgres_changes",
"severity": "P0",
"table": "users",
"subscription_test": {
"channel": "realtime:public:users",
"subscribed": true,
"events_received": true
},
"sample_event": {
"type": "INSERT",
"table": "users",
"record": {
"id": "[REDACTED]",
"email": "[REDACTED]@example.com",
"name": "[REDACTED]"
},
"redacted": true
},
"impact": {
"pii_streaming": true,
"affected_columns": ["email", "name"],
"rls_bypass": true
},
"websocket_url": "wss://abc123def.supabase.co/realtime/v1/websocket",
"reproduction_code": "const channel = supabase.channel('realtime:public:users').on('postgres_changes', { event: '*', schema: 'public', table: 'users' }, (payload) => console.log(payload)).subscribe()"
}
supabase-audit-rls — RLS affects Realtimesupabase-audit-tables-read — API access is relatedsupabase-report — Include in final reportWeekly Installs
96
Repository
GitHub Stars
32
First Seen
Jan 31, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
claude-code76
codex71
opencode70
gemini-cli66
github-copilot62
cursor60
Supabase Postgres 最佳实践指南 - 8大类别性能优化规则与SQL示例
73,500 周安装