List Exposed Tables
🔴 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 discovery
- Log to
.sb-pentest-audit.log BEFORE and AFTER each action
- 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 discovers all database tables exposed through the Supabase PostgREST API.
When to Use This Skill
- To understand the API attack surface
- Before testing RLS policies
- To inventory exposed data models
- As part of a comprehensive security audit
Prerequisites
- Supabase URL extracted (auto-invokes if needed)
- Anon key extracted (auto-invokes if needed)
How It Works
Supabase exposes tables via PostgREST at:
https://[project-ref].supabase.co/rest/v1/
The skill uses the OpenAPI schema endpoint to enumerate tables:
https://[project-ref].supabase.co/rest/v1/?apikey=[anon-key]
What Gets Exposed
By default, Supabase exposes tables in the public schema. Tables are exposed when:
- They exist in an exposed schema (default:
public)
- No explicit
REVOKE has been done
- PostgREST can see them
Usage
Basic Table List
List tables exposed on my Supabase project
List all exposed tables with column details
═══════════════════════════════════════════════════════════
EXPOSED TABLES
═══════════════════════════════════════════════════════════
Project: abc123def.supabase.co
Schema: public
Tables Found: 8
─────────────────────────────────────────────────────────
Table Inventory
─────────────────────────────────────────────────────────
1. users
├── Columns: id, email, name, avatar_url, created_at
├── Primary Key: id (uuid)
├── RLS Status: Unknown (test with supabase-audit-rls)
└── Risk: ⚠️ Contains user PII
2. profiles
├── Columns: id, user_id, bio, website, social_links
├── Primary Key: id (uuid)
├── Foreign Key: user_id → auth.users
└── Risk: ⚠️ Contains user PII
3. posts
├── Columns: id, author_id, title, content, published, created_at
├── Primary Key: id (uuid)
└── Risk: ℹ️ Content data
4. comments
├── Columns: id, post_id, user_id, content, created_at
├── Primary Key: id (uuid)
└── Risk: ℹ️ Content data
5. orders
├── Columns: id, user_id, total, status, items, created_at
├── Primary Key: id (uuid)
└── Risk: 🔴 Contains financial/transaction data
6. products
├── Columns: id, name, description, price, stock, image_url
├── Primary Key: id (uuid)
└── Risk: ℹ️ Public catalog data
7. settings
├── Columns: id, key, value, updated_at
├── Primary Key: id (uuid)
└── Risk: ⚠️ May contain sensitive configuration
8. api_keys
├── Columns: id, user_id, key_hash, name, last_used
├── Primary Key: id (uuid)
└── Risk: 🔴 Contains secrets
─────────────────────────────────────────────────────────
Summary
─────────────────────────────────────────────────────────
Total Tables: 8
High Risk: 2 (orders, api_keys)
Medium Risk: 3 (users, profiles, settings)
Low Risk: 3 (posts, comments, products)
Next Steps:
├── Run supabase-audit-tables-read to test actual data access
├── Run supabase-audit-rls to verify RLS policies
└── Review high-risk tables first
═══════════════════════════════════════════════════════════
Risk Classification
Tables are classified by likely content:
| Risk | Table Patterns | Examples |
|---|
| 🔴 High | Financial, secrets, auth | orders, payments, api_keys, secrets |
| ⚠️ Medium | User PII, config | users, profiles, settings, preferences |
| ℹ️ Low | Public content | posts, products, categories, tags |
Context Output
json
1{
2 "tables": {
3 "count": 8,
4 "list": [
5 {
6 "name": "users",
7 "schema": "public",
8 "columns": ["id", "email", "name", "avatar_url", "created_at"],
9 "primary_key": "id",
10 "risk_level": "medium",
11 "risk_reason": "Contains user PII"
12 },
13 {
14 "name": "orders",
15 "schema": "public",
16 "columns": ["id", "user_id", "total", "status", "items", "created_at"],
17 "primary_key": "id",
18 "risk_level": "high",
19 "risk_reason": "Contains financial data"
20 }
21 ],
22 "by_risk": {
23 "high": ["orders", "api_keys"],
24 "medium": ["users", "profiles", "settings"],
25 "low": ["posts", "comments", "products"]
26 }
27 }
28}
Hidden Tables
Some tables may not appear in the OpenAPI schema:
═══════════════════════════════════════════════════════════
ADDITIONAL DISCOVERY
═══════════════════════════════════════════════════════════
Common Tables Not in Schema (testing existence):
├── _prisma_migrations: ❌ Not found
├── schema_migrations: ❌ Not found
├── audit_log: ✅ EXISTS but not in OpenAPI
└── internal_config: ❌ Not found
Note: 'audit_log' exists but may have restricted access.
Test with supabase-audit-tables-read.
═══════════════════════════════════════════════════════════
Schema Analysis
The skill also checks for non-public schemas:
Schema Exposure Check:
├── public: ✅ Exposed (8 tables)
├── auth: ❌ Not directly exposed (expected)
├── storage: ❌ Not directly exposed (expected)
├── extensions: ❌ Not exposed (good)
└── custom_schema: ⚠️ Exposed (3 tables) - Review if intentional
Common Issues
❌ Problem: No tables found
✅ Solution:
- Check if anon key is valid
- Verify project URL is correct
- The API may be disabled in project settings
❌ Problem: Too many tables listed
✅ Solution: This may indicate overly permissive schema exposure. Consider:
sql
1-- Restrict exposed schemas
2ALTER ROLE anon SET search_path TO public;
❌ Problem: Sensitive tables exposed
✅ Solution: Either remove from public schema or implement strict RLS.
Recommendations by Table Type
User Tables
sql
1-- Ensure RLS is enabled
2ALTER TABLE users ENABLE ROW LEVEL SECURITY;
3
4-- Users can only see their own data
5CREATE POLICY "Users see own data" ON users
6 FOR SELECT USING (auth.uid() = id);
Order/Payment Tables
sql
1-- Strict RLS for financial data
2ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
3
4CREATE POLICY "Users see own orders" ON orders
5 FOR SELECT USING (auth.uid() = user_id);
6
7-- No public access even for admins via API
8-- Use Edge Functions for admin operations
Secret Tables
sql
1-- Consider not exposing at all
2REVOKE ALL ON TABLE api_keys FROM anon, authenticated;
3
4-- Or use views that hide sensitive columns
5CREATE VIEW public.api_keys_safe AS
6 SELECT id, name, last_used FROM api_keys;
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 starting any action → Log the action to
.sb-pentest-audit.log
- After each table discovered → Immediately update
.sb-pentest-context.json
- After each significant step → Log completion to
.sb-pentest-audit.log
This ensures that if the skill is interrupted, crashes, or times out, all findings up to that point are preserved.
Required Actions (Progressive)
-
Update .sb-pentest-context.json with results:
json
1{
2 "tables": {
3 "count": 8,
4 "list": [ ... ],
5 "by_risk": { "high": [], "medium": [], "low": [] }
6 }
7}
-
Log to .sb-pentest-audit.log:
[TIMESTAMP] [supabase-audit-tables-list] [START] Listing exposed tables
[TIMESTAMP] [supabase-audit-tables-list] [SUCCESS] Found 8 tables
[TIMESTAMP] [supabase-audit-tables-list] [CONTEXT_UPDATED] .sb-pentest-context.json updated
-
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/03-api-audit/tables/
Evidence Files to Create
| File | Content |
|---|
tables-list.json | Complete list of exposed tables |
tables-metadata.json | Column details and types per table |
openapi-schema.json | Raw OpenAPI/PostgREST schema |
json
1{
2 "evidence_id": "API-TBL-001",
3 "timestamp": "2025-01-31T10:15:00Z",
4 "category": "api-audit",
5 "type": "table_enumeration",
6
7 "request": {
8 "method": "GET",
9 "url": "https://abc123def.supabase.co/rest/v1/",
10 "headers": {
11 "apikey": "[REDACTED]"
12 },
13 "curl_command": "curl -s 'https://abc123def.supabase.co/rest/v1/' -H 'apikey: $ANON_KEY'"
14 },
15
16 "tables_found": [
17 {
18 "name": "users",
19 "schema": "public",
20 "columns": ["id", "email", "name", "created_at"],
21 "primary_key": "id",
22 "risk_level": "high",
23 "risk_reason": "Contains PII"
24 },
25 {
26 "name": "orders",
27 "schema": "public",
28 "columns": ["id", "user_id", "total", "status"],
29 "primary_key": "id",
30 "risk_level": "high",
31 "risk_reason": "Financial data"
32 }
33 ],
34
35 "summary": {
36 "total_tables": 8,
37 "high_risk": 2,
38 "medium_risk": 3,
39 "low_risk": 3
40 }
41}
Add to curl-commands.sh
bash
1# === TABLE ENUMERATION ===
2# List all exposed tables via OpenAPI schema
3curl -s "$SUPABASE_URL/rest/v1/" -H "apikey: $ANON_KEY"
supabase-audit-tables-read — Test actual data access
supabase-audit-rls — Verify RLS policies
supabase-audit-rpc — Check exposed functions