supabase-audit-realtime
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRealtime Channel Audit
Realtime频道审计
🔴 CRITICAL: PROGRESSIVE FILE UPDATES REQUIREDYou MUST write to context files AS YOU GO, not just at the end.
- Write to
IMMEDIATELY after each channel tested.sb-pentest-context.json- Log to
BEFORE and AFTER each subscription test.sb-pentest-audit.log- 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.
🔴 严重警告:需逐步更新文件你必须在操作过程中写入上下文文件,而不是仅在最后统一写入。
- 每测试完一个频道后,立即写入
.sb-pentest-context.json- 在每次订阅测试前后,将操作记录到
.sb-pentest-audit.log- 禁止等到技能执行完成后再更新文件
- 如果技能崩溃或被中断,所有已发现的问题必须已保存
此要求为强制项,未逐步更新文件属于严重错误。
本技能用于测试Supabase Realtime WebSocket频道的安全问题。
When to Use This Skill
适用场景
- To check if Realtime channels are properly secured
- To detect unauthorized data streaming
- When Realtime is used for sensitive data
- As part of comprehensive security audit
- 检查Realtime频道是否已正确配置安全防护
- 检测是否存在未授权的数据流
- 当Realtime用于传输敏感数据时
- 作为全面安全审计的一部分
Prerequisites
前置条件
- Supabase URL and anon key available
- Detection completed
- 已获取Supabase URL和匿名密钥
- 已完成检测准备工作
Understanding Supabase Realtime
了解Supabase Realtime
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 |
Supabase Realtime支持以下端点:
wss://[project].supabase.co/realtime/v1/websocket| 功能 | 描述 |
|---|---|
| Postgres Changes | 流式传输数据库变更 |
| Broadcast | 发布/订阅消息传递 |
| Presence | 用户在线状态追踪 |
Security Model
安全模型
Realtime respects RLS policies:
- ✅ If RLS blocks SELECT, Realtime won't stream
- ❌ If RLS allows SELECT, Realtime streams data
- ⚠️ Broadcast channels can be subscribed without RLS
Realtime会遵循RLS策略:
- ✅ 如果RLS阻止SELECT操作,Realtime不会传输数据
- ❌ 如果RLS允许SELECT操作,Realtime会传输对应数据
- ⚠️ Broadcast频道可绕过RLS直接订阅
Tests Performed
执行的测试项
| Test | Purpose |
|---|---|
| Channel enumeration | Find open channels |
| Postgres Changes | Test table streaming |
| Broadcast | Test pub/sub access |
| Presence | Test presence channel access |
| 测试 | 目的 |
|---|---|
| 频道枚举 | 发现开放的频道 |
| Postgres Changes | 测试表数据的流式传输 |
| Broadcast | 测试发布/订阅的访问权限 |
| Presence | 测试在线状态频道的访问权限 |
Usage
使用方法
Basic Realtime Audit
基础Realtime审计
Audit Realtime channels on my Supabase project审计我的Supabase项目的Realtime频道Test Specific Feature
测试特定功能
Test if Postgres Changes streams sensitive data测试Postgres Changes是否传输敏感数据Output Format
输出格式
═══════════════════════════════════════════════════════════
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:
json
{
"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:
json
{
"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:sql
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:javascript
// 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:javascript
channel.track({ online_at: new Date().toISOString() // Don't include email, user_id unless needed })
═══════════════════════════════════════════════════════════
undefined═══════════════════════════════════════════════════════════
REALTIME频道审计报告
═══════════════════════════════════════════════════════════
项目:abc123def.supabase.co
端点:wss://abc123def.supabase.co/realtime/v1/websocket
─────────────────────────────────────────────────────────
连接测试
─────────────────────────────────────────────────────────
WebSocket连接:✅ 已建立
身份验证:匿名密钥已通过
协议:Phoenix channels
─────────────────────────────────────────────────────────
Postgres Changes测试
─────────────────────────────────────────────────────────
使用匿名密钥订阅表变更...
表: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 - 用户数据在无身份验证的情况下被流式传输!
可能未为Realtime正确配置RLS策略。
表:orders
├── 订阅状态:✅ 已订阅
├── INSERT事件:❌ 无法接收(RLS策略生效)
├── UPDATE事件:❌ 无法接收(RLS策略生效)
└── DELETE事件:❌ 无法接收(RLS策略生效)
评估结果:✅ orders表已被正确防护。
表:posts
├── 订阅状态:✅ 已订阅
├── INSERT事件:✅ 仅可接收已发布内容
├── UPDATE事件:✅ 仅可接收已发布内容
└── DELETE事件:✅ 仅可接收已发布内容
评估结果:✅ Posts数据传输遵循RLS策略(仅已发布内容)。
─────────────────────────────────────────────────────────
Broadcast频道测试
─────────────────────────────────────────────────────────
尝试订阅常见频道名称...
频道:room:lobby
├── 订阅状态:✅ 成功
├── 消息接收:可接收广播消息
└── 评估结果:ℹ️ 开放频道(可能为预期配置)
频道:admin
├── 订阅状态:✅ 成功 ← 该频道是否应公开访问?
├── 消息接收:可接收管理员通知
└── 评估结果:🟠 P1 - 管理员频道可被公开访问
频道:notifications
├── 订阅状态:✅ 成功
├── 消息接收:可接收所有用户的通知消息!
└── 评估结果:🔴 P0 - 用户通知数据已暴露
收到的示例通知:
json
{
"user_id": "123...",
"type": "payment_received",
"amount": 150.00,
"from": "customer@example.com"
}─────────────────────────────────────────────────────────
Presence测试
─────────────────────────────────────────────────────────
频道:online-users
├── 订阅状态:✅ 成功
├── 在线列表:可查看所有在线用户
└── 当前在线用户数:47
收到的示例在线状态数据:
json
{
"user_id": "550e8400-...",
"email": "user@example.com",
"status": "online",
"last_seen": "2025-01-31T14:00:00Z"
}评估结果:🟠 P1 - 用户在线状态数据已暴露
需评估是否应公开邮箱/user_id等信息。
─────────────────────────────────────────────────────────
总结
─────────────────────────────────────────────────────────
Postgres Changes:
├── 🔴 P0: users表所有数据被流式传输
├── ✅ 通过:orders表受RLS策略防护
└── ✅ 通过:posts表过滤规则正确
Broadcast:
├── 🔴 P0: notifications频道暴露用户数据
├── 🟠 P1: admin频道可被公开访问
└── ℹ️ 信息:lobby频道开放(需确认是否为预期配置)
Presence:
└── 🟠 P1: online-users频道暴露用户详情
严重问题:2个
高危问题:2个
═══════════════════════════════════════════════════════════
修复建议
═══════════════════════════════════════════════════════════
-
修复users表的RLS策略 确保RLS策略应用于Realtime:sql
ALTER TABLE users ENABLE ROW LEVEL SECURITY; CREATE POLICY "用户仅可查看自身数据" ON users FOR SELECT USING (auth.uid() = id); -
加固Broadcast频道 使用Realtime授权机制:javascript
// 敏感频道需身份验证 const channel = supabase.channel('admin', { config: { broadcast: { ack: true }, presence: { key: userId } } }) // 服务端:验证频道访问权限 // 在realtime.channels表上配置RLS策略 -
限制Presence数据范围 仅共享必要信息:javascript
channel.track({ online_at: new Date().toISOString() // 除非必要,否则不要包含邮箱、user_id等信息 })
═══════════════════════════════════════════════════════════
undefinedRealtime Security Model
Realtime安全模型
Postgres Changes + RLS
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 changessql
-- 此RLS策略同样适用于Realtime
CREATE POLICY "用户仅可查看自身数据"
ON users FOR SELECT
USING (auth.uid() = id);
-- 配置该策略后:
-- - API SELECT:仅可查看自身数据
-- - Realtime:仅可接收自身数据的变更Broadcast Security
Broadcast安全
sql
-- 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())
);sql
-- Realtime授权(Supabase扩展功能)
-- 在realtime.channels虚拟表上配置策略
-- 仅认证用户可加入频道
CREATE POLICY "认证用户可加入频道"
ON realtime.channels FOR SELECT
USING (auth.role() = 'authenticated');
-- 或限制特定频道的访问权限
CREATE POLICY "管理员仅可访问admin频道"
ON realtime.channels FOR SELECT
USING (
name != 'admin' OR
(SELECT is_admin FROM profiles WHERE id = auth.uid())
);Context Output
上下文输出
json
{
"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"
}
}
}
}json
{
"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"
}
}
}
}Common Realtime Issues
常见Realtime安全问题
| 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 |
| 问题 | 原因 | 修复方案 |
|---|---|---|
| 全量数据被流式传输 | 未启用/配置RLS策略 | 启用并正确配置RLS |
| Broadcast频道开放 | 未配置频道授权机制 | 添加频道访问策略 |
| Presence数据暴露 | 追踪了过多不必要数据 | 最小化追踪的数据范围 |
Remediation Examples
修复示例
Secure Table Streaming
加固表数据流式传输
sql
-- 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 rowsql
-- 确保已启用RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- 配置仅认证用户可访问自身数据的策略
CREATE POLICY "用户仅可查看自身资料" ON users
FOR SELECT
USING (auth.uid() = id);
-- 此后Realtime仅会传输已认证用户自身数据的变更Secure Broadcast Channels
加固Broadcast频道
javascript
// 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();
}javascript
-- 客户端:订阅前检查访问权限
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();
}Minimal Presence Data
最小化Presence数据
javascript
// 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
});javascript
// 优化前(数据过多)
channel.track({
user_id: userId,
email: email,
name: fullName,
avatar: avatarUrl
});
// 优化后(仅必要数据)
channel.track({
online_at: new Date().toISOString()
// 用户详情可在需要时单独获取
});MANDATORY: Progressive Context File Updates
强制要求:逐步更新上下文文件
⚠️ This skill MUST update tracking files PROGRESSIVELY during execution, NOT just at the end.
⚠️ 本技能必须在执行过程中逐步更新追踪文件,而非仅在最后统一更新。
Critical Rule: Write As You Go
核心规则:边操作边写入
DO NOT batch all writes at the end. Instead:
- Before testing each channel → Log the action to
.sb-pentest-audit.log - After each data exposure found → Immediately update
.sb-pentest-context.json - After each subscription test → Log the result immediately
This ensures that if the skill is interrupted, crashes, or times out, all findings up to that point are preserved.
禁止批量写入所有内容。正确流程:
- 测试每个频道前 → 将操作记录到
.sb-pentest-audit.log - 发现数据暴露问题后 → 立即更新
.sb-pentest-context.json - 每个订阅测试完成后 → 立即记录测试结果
此规则可确保即使技能被中断、崩溃或超时,所有已发现的问题都已被保存。
Required Actions (Progressive)
需执行的逐步操作
-
Updatewith results:
.sb-pentest-context.jsonjson{ "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.
-
**更新**以记录测试结果:
.sb-pentest-context.jsonjson{ "realtime_audit": { "timestamp": "...", "connection": "established", "postgres_changes": { ... }, "broadcast": { ... }, "presence": { ... } } } -
记录到:
.sb-pentest-audit.log[时间戳] [supabase-audit-realtime] [开始] 审计Realtime频道 [时间戳] [supabase-audit-realtime] [发现问题] P0: users表全量数据被流式传输 [时间戳] [supabase-audit-realtime] [上下文已更新] .sb-pentest-context.json已更新 -
若文件不存在,需先创建再写入。
未更新上下文文件的行为是不被允许的。
MANDATORY: Evidence Collection
强制要求:收集证据
📁 Evidence Directory:
.sb-pentest-evidence/06-realtime-audit/📁 证据目录:
.sb-pentest-evidence/06-realtime-audit/Evidence Files to Create
需创建的证据文件
| File | Content |
|---|---|
| WebSocket connection test |
| Table subscription results |
| Broadcast channel access |
| Presence data exposure |
| 文件 | 内容 |
|---|---|
| WebSocket连接测试结果 |
| 表订阅测试结果 |
| Broadcast频道访问情况 |
| Presence数据暴露情况 |
Evidence Format
证据文件格式
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()"
}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": "[已脱敏]",
"email": "[已脱敏]@example.com",
"name": "[已脱敏]"
},
"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()"
}Related Skills
相关技能
- — RLS affects Realtime
supabase-audit-rls - — API access is related
supabase-audit-tables-read - — Include in final report
supabase-report
- — RLS策略会影响Realtime的行为
supabase-audit-rls - — API访问权限与Realtime相关
supabase-audit-tables-read - — 可将本审计结果纳入最终报告
supabase-report