supabase-audit-realtime について
Supabase Realtime の WebSocket チャンネルを検査するセキュリティ監査スキルです。Postgres Changes、Broadcast、Presence のアクセスを調べ、未認可の購読やデータ露出を確認し、結果と証拠を逐次記録します。
主な機能
- 01アクセス可能なチャンネルの列挙
- 02Postgres Changes の購読テスト
- 03Broadcast のアクセス制御確認
- 04Presence のデータ露出確認
- 05調査結果と証拠の逐次記録
活用シーン
- 01テーブル変更の配信が RLS の権限を超えていないか確認
- 02Broadcast チャンネルの認可を評価
- 03Presence で共有される機密情報を確認
インストール
コマンドをコピーしてターミナルで実行します。
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 はデータをストリーミングします
- ⚠️ Broadcast チャンネルは RLS なしでサブスクライブできます
実行されるテスト
| テスト | 目的 |
|---|---|
| チャンネルの列挙 | オープンなチャンネルを見つけます |
| Postgres Changes | テーブルのストリーミングをテストします |
| Broadcast | パブリッシュ/サブスクライブへのアクセスをテストします |
| 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 - ユーザーのプレゼンスデータが露出しています メールアドレス/user_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 Authorization を使用してください:
// 機密チャンネルには認証を必須にする 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 | プレゼンスデータの露出 |
証拠の形式
{
"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 は Realtime に影響しますsupabase-audit-tables-read— API アクセスに関連しますsupabase-report— 最終レポートに含めます