关于 supabase-audit-realtime
用于测试 Supabase Realtime WebSocket 频道的安全审计技能。它检查 Postgres Changes、Broadcast 和 Presence 的访问权限,识别未授权订阅或数据暴露,并逐步记录结果与证据。
主要功能
- 01枚举可访问的频道
- 02测试 Postgres Changes 订阅
- 03检查 Broadcast 访问控制
- 04检查 Presence 数据暴露
- 05逐步记录发现与证据
适用场景
- 01检查表变更是否超出 RLS 权限范围而被推送
- 02评估 Broadcast 频道的授权机制
- 03审查 Presence 共享的敏感信息
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yoanbernabeu/ supabase-pentest-skills --skill supabase-audit-realtime安装后在 Claude Code 中输入/supabase-audit-realtime确认已加载。
详细介绍
实时频道审计
🔴 严重:必须逐步更新文件
必须边操作边写入上下文文件,不能只在最后写入。
- 每测试完一个频道后,立即写入
.sb-pentest-context.json- 每次订阅测试之前和之后,都要记录到
.sb-pentest-audit.log- 不要等技能完成后才更新文件
- 如果技能崩溃或中断,之前的所有发现都必须已保存
此要求不是可选项。未能逐步写入属于严重错误。
此技能用于测试 Supabase Realtime WebSocket 频道是否存在安全问题。
何时使用此技能
- 检查 Realtime 频道是否受到妥善保护
- 检测未经授权的数据流
- Realtime 用于敏感数据时
- 作为全面安全审计的一部分
前提条件
- 已获取 Supabase URL 和 anon key
- 已完成检测
了解 Supabase Realtime
Supabase Realtime 支持:
wss://[project].supabase.co/realtime/v1/websocket
| 功能 | 描述 |
|---|---|
| Postgres Changes | 流式传输数据库变更 |
| Broadcast | 发布/订阅消息 |
| Presence | 用户在线状态跟踪 |
安全模型
Realtime 遵循 RLS 策略:
- ✅ 如果 RLS 阻止 SELECT,Realtime 就不会流式传输数据
- ❌ 如果 RLS 允许 SELECT,Realtime 就会流式传输数据
- ⚠️ 无需 RLS 即可订阅 Broadcast 频道
执行的测试
| 测试 | 目的 |
|---|---|
| 频道枚举 | 查找开放频道 |
| Postgres Changes | 测试表数据流 |
| Broadcast | 测试发布/订阅访问权限 |
| Presence | 测试 Presence 频道访问权限 |
用法
Realtime 基础审计
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": "[email protected]", ← PII STREAMING!
"name": "New User",
"created_at": "2025-01-31T10:00:00Z"
}
}
发现:🔴 P0 - 未经身份验证就流式传输用户数据! Realtime 的 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": "[email protected]"
}
───────────────────────────────────────────────────────── 在线状态测试 ─────────────────────────────────────────────────────────
频道:online-users ├── 订阅:✅ 成功 ├── 在线状态列表:正在接收所有在线用户 └── 在线用户数:47
在线状态数据示例:
{
"user_id": "550e8400-...",
"email": "[email protected]",
"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 应用于 Realtime:
ALTER TABLE users ENABLE ROW LEVEL SECURITY; CREATE POLICY "Users see only themselves" ON users FOR SELECT USING (auth.uid() = id); -
保护广播频道 使用 Realtime 授权:
// 对敏感频道要求身份验证 const channel = supabase.channel('admin', { config: { broadcast: { ack: true }, presence: { key: userId } } }) // 服务端:验证频道访问权限 // 在 realtime.channels 表上使用 RLS -
限制在线状态数据 仅分享必要信息:
channel.track({ online_at: new Date().toISOString() // 除非必要,否则不要包含 email、user_id })
═══════════════════════════════════════════════════════════
## 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"
}
}
}
}
常见 Realtime 问题
| 问题 | 原因 | 修复方法 |
|---|---|---|
| 正在流式传输所有数据 | 未启用/配置 RLS | 启用并配置 RLS |
| 广播开放 | 未设置频道授权 | 添加频道策略 |
| 在线状态暴露 | 跟踪的数据过多 | 尽量减少跟踪的数据 |
修复示例
安全的表流式传输
-- 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
});
强制要求:逐步更新上下文文件
⚠️ 此技能必须在执行过程中逐步更新跟踪文件,而不是只在最后更新。
关键规则:边做边写
不要把所有写入操作都留到最后。请改为:
- 测试每个频道之前 → 将操作记录到
.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] 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 -
如果文件不存在,请先创建再写入。
无法接受未更新上下文文件的情况。
强制要求:收集证据
📁 证据目录: .sb-pentest-evidence/06-realtime-audit/
需要创建的证据文件
| 文件 | 内容 |
|---|---|
websocket-connection.json | WebSocket 连接测试 |
postgres-changes/[table].json | 表订阅结果 |
broadcast-channels/[channel].json | 广播频道访问权限 |
presence-data/[channel].json | Presence 数据暴露情况 |
证据格式
{
"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 会影响 Realtimesupabase-audit-tables-read— API 访问与此相关supabase-report— 纳入最终报告