supabase-audit-realtime

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Realtime Channel Audit

Realtime频道审计

🔴 CRITICAL: PROGRESSIVE FILE UPDATES REQUIRED
You MUST write to context files AS YOU GO, not just at the end.
  • Write to
    .sb-pentest-context.json
    IMMEDIATELY after each channel tested
  • Log to
    .sb-pentest-audit.log
    BEFORE 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.
🔴 严重警告:需逐步更新文件
你必须在操作过程中写入上下文文件,而不是仅在最后统一写入。
  • 每测试完一个频道后,立即写入
    .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
FeatureDescription
Postgres ChangesStream database changes
BroadcastPub/sub messaging
PresenceUser 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

执行的测试项

TestPurpose
Channel enumerationFind open channels
Postgres ChangesTest table streaming
BroadcastTest pub/sub access
PresenceTest 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 ═══════════════════════════════════════════════════════════
  1. 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);
  2. 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
  3. 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个
═══════════════════════════════════════════════════════════ 修复建议 ═══════════════════════════════════════════════════════════
  1. 修复users表的RLS策略 确保RLS策略应用于Realtime:
    sql
    ALTER TABLE users ENABLE ROW LEVEL SECURITY;
    
    CREATE POLICY "用户仅可查看自身数据"
      ON users FOR SELECT
      USING (auth.uid() = id);
  2. 加固Broadcast频道 使用Realtime授权机制:
    javascript
    // 敏感频道需身份验证
    const channel = supabase.channel('admin', {
      config: {
        broadcast: { ack: true },
        presence: { key: userId }
      }
    })
    
    // 服务端:验证频道访问权限
    // 在realtime.channels表上配置RLS策略
  3. 限制Presence数据范围 仅共享必要信息:
    javascript
    channel.track({
      online_at: new Date().toISOString()
      // 除非必要,否则不要包含邮箱、user_id等信息
    })
═══════════════════════════════════════════════════════════
undefined

Realtime 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 changes
sql
-- 此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安全问题

IssueCauseFix
All data streamingRLS not enabled/configuredEnable and configure RLS
Broadcast openNo channel authorizationAdd channel policies
Presence exposedToo much data trackedMinimize 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 row
sql
-- 确保已启用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:
  1. Before testing each channel → Log the action to
    .sb-pentest-audit.log
  2. After each data exposure found → Immediately update
    .sb-pentest-context.json
  3. 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.
禁止批量写入所有内容。正确流程:
  1. 测试每个频道前 → 将操作记录到
    .sb-pentest-audit.log
  2. 发现数据暴露问题后 → 立即更新
    .sb-pentest-context.json
  3. 每个订阅测试完成后 → 立即记录测试结果
此规则可确保即使技能被中断、崩溃或超时,所有已发现的问题都已被保存。

Required Actions (Progressive)

需执行的逐步操作

  1. Update
    .sb-pentest-context.json
    with results:
    json
    {
      "realtime_audit": {
        "timestamp": "...",
        "connection": "established",
        "postgres_changes": { ... },
        "broadcast": { ... },
        "presence": { ... }
      }
    }
  2. 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
  3. If files don't exist, create them before writing.
FAILURE TO UPDATE CONTEXT FILES IS NOT ACCEPTABLE.
  1. **更新
    .sb-pentest-context.json
    **以记录测试结果:
    json
    {
      "realtime_audit": {
        "timestamp": "...",
        "connection": "established",
        "postgres_changes": { ... },
        "broadcast": { ... },
        "presence": { ... }
      }
    }
  2. 记录到
    .sb-pentest-audit.log
    [时间戳] [supabase-audit-realtime] [开始] 审计Realtime频道
    [时间戳] [supabase-audit-realtime] [发现问题] P0: users表全量数据被流式传输
    [时间戳] [supabase-audit-realtime] [上下文已更新] .sb-pentest-context.json已更新
  3. 若文件不存在,需先创建再写入。
未更新上下文文件的行为是不被允许的。

MANDATORY: Evidence Collection

强制要求:收集证据

📁 Evidence Directory:
.sb-pentest-evidence/06-realtime-audit/
📁 证据目录:
.sb-pentest-evidence/06-realtime-audit/

Evidence Files to Create

需创建的证据文件

FileContent
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
文件内容
websocket-connection.json
WebSocket连接测试结果
postgres-changes/[table].json
表订阅测试结果
broadcast-channels/[channel].json
Broadcast频道访问情况
presence-data/[channel].json
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

相关技能

  • supabase-audit-rls
    — RLS affects Realtime
  • supabase-audit-tables-read
    — API access is related
  • supabase-report
    — Include in final report
  • supabase-audit-rls
    — RLS策略会影响Realtime的行为
  • supabase-audit-tables-read
    — API访问权限与Realtime相关
  • supabase-report
    — 可将本审计结果纳入最终报告