supabase-audit-realtime
Test Supabase Realtime WebSocket channels for unauthorized subscriptions and data exposure.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install yoanbernabeu-supabase-pentest-skills-supabase-audit-realtime
Repository
Skill path: skills/audit-realtime/supabase-audit-realtime
Test Supabase Realtime WebSocket channels for unauthorized subscriptions and data exposure.
Open repositoryBest for
Primary workflow: Analyze Data & AI.
Technical facets: Full Stack, Data / AI, Testing.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: yoanbernabeu.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install supabase-audit-realtime into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/yoanbernabeu/supabase-pentest-skills before adding supabase-audit-realtime to shared team environments
- Use supabase-audit-realtime for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: supabase-audit-realtime
description: Test Supabase Realtime WebSocket channels for unauthorized subscriptions and data exposure.
---
# Realtime Channel Audit
> π΄ **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.
## 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
## Prerequisites
- Supabase URL and anon key available
- Detection completed
## Understanding 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 |
## 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
## Tests Performed
| Test | Purpose |
|------|---------|
| Channel enumeration | Find open channels |
| Postgres Changes | Test table streaming |
| Broadcast | Test pub/sub access |
| Presence | Test presence channel access |
## Usage
### Basic Realtime Audit
```
Audit Realtime channels on my Supabase project
```
### Test Specific Feature
```
Test if Postgres Changes streams sensitive data
```
## 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": "[email protected]", β 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": "[email protected]"
}
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Presence Test
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Channel: online-users
βββ Subscribe: β
Success
βββ Presence List: Receiving all online users
βββ Users Online: 47
Sample Presence Data:
```json
{
"user_id": "550e8400-...",
"email": "[email protected]",
"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
})
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
## 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
```
### Broadcast Security
```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())
);
```
## 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"
}
}
}
}
```
## Common Realtime Issues
| 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 |
## 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
```
### Secure Broadcast Channels
```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();
}
```
### Minimal Presence Data
```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
});
```
## 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.
### 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.**
## MANDATORY: Evidence Collection
π **Evidence Directory:** `.sb-pentest-evidence/06-realtime-audit/`
### Evidence Files to Create
| 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 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()"
}
```
## Related Skills
- `supabase-audit-rls` β RLS affects Realtime
- `supabase-audit-tables-read` β API access is related
- `supabase-report` β Include in final report