supabase-audit-tables-list — Claude Code skill: what it does
Enumerates tables exposed through the Supabase PostgREST API so you can see the attack surface before testing deeper.
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-list -a claude-codeWhat supabase-audit-tables-list does
The skill queries the Supabase OpenAPI schema at https://[project-ref].supabase.co/rest/v1/?apikey=[anon-key] and builds an inventory of tables reachable in the public schema. For each table it lists columns, the primary key, and a risk tag: financial or secret-holding tables get flagged high risk, tables with user data get a medium tag.
Beyond what the OpenAPI schema returns, the skill probes for tables that exist in the database but don't show up there, such as audit_log, and checks whether schemas other than public (auth, storage, custom ones) are reachable from outside. It needs a Supabase project URL and an anon key beforehand; if those are missing it triggers their extraction first.
Findings get written to context files as the scan progresses rather than only at the end, which matters if the run gets interrupted. The output feeds directly into checking RLS policies or testing actual row access next.
What it helps with
- map which tables are reachable via the Supabase PostgREST API
- flag tables by risk level based on their likely data
- spot tables hidden from the OpenAPI schema but still present
- check whether non-public schemas are exposed
- build a table inventory before testing RLS policies
Example prompts for the agent
- List tables exposed on my Supabase project
- List all exposed tables with column details
- Check which schemas besides public are reachable through PostgREST
How to install supabase-audit-tables-list
1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-list -a claude-code2Manually: put the files in place
~/.claude/skills/supabase-audit-tables-list/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/supabase-audit-tables-list/
1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-list -a codex2Manually: put the files in place
.agents/skills/supabase-audit-tables-list/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-list -a cursor2Manually: put the files in place
.cursor/skills/supabase-audit-tables-list/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install yoanbernabeu/supabase-pentest-skills supabase-audit-tables-list2Manually: put the files in place
.github/skills/supabase-audit-tables-list/SKILL.md1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-list -a gemini-cli2Platform CLI
gemini skills install https://github.com/yoanbernabeu/supabase-pentest-skills --path skills/audit-api/supabase-audit-tables-listManually: put the files in place
.devin/skills/supabase-audit-tables-list/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-list -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about supabase-audit-tables-list
How do I install supabase-audit-tables-list in Claude Code?
Run npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-list -a claude-code; the skill then lives at ~/.claude/skills/supabase-audit-tables-list/SKILL.md.
What does supabase-audit-tables-list do?
It enumerates every table reachable through the Supabase PostgREST API, lists their columns and primary keys, and tags each table with a risk level based on its likely content.
What do I need before running this skill?
You need the Supabase project URL and an anon key; if they're missing, the skill triggers their extraction before scanning tables.
Source text
SKILL.md · MIT
---
name: supabase-audit-tables-list
description: List all tables exposed via the Supabase PostgREST API to identify the attack surface.
---
# 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:
1. They exist in an exposed schema (default: `public`)
2. No explicit `REVOKE` has been done
3. PostgREST can see them
## Usage
### Basic Table List
```
List tables exposed on my Supabase project
```
### With Schema Information
```
List all exposed tables with column details
```
## Output Format
```
═══════════════════════════════════════════════════════════
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
{
"tables": {
"count": 8,
"list": [
{
"name": "users",
"schema": "public",
"columns": ["id", "email", "name", "avatar_url", "created_at"],
"primary_key": "id",
"risk_level": "medium",
"risk_reason": "Contains user PII"
},
{
"name": "orders",
"schema": "public",
"columns": ["id", "user_id", "total", "status", "items", "created_at"],
"primary_key": "id",
"risk_level": "high",
"risk_reason": "Contains financial data"
}
],
"by_risk": {
"high": ["orders", "api_keys"],
"medium": ["users", "profiles", "settings"],
"low": ["posts", "comments", "products"]
}
}
}
```
## 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
-- Restrict exposed schemas
ALTER 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
-- Ensure RLS is enabled
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Users can only see their own data
CREATE POLICY "Users see own data" ON users
FOR SELECT USING (auth.uid() = id);
```
### Order/Payment Tables
```sql
-- Strict RLS for financial data
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see own orders" ON orders
FOR SELECT USING (auth.uid() = user_id);
-- No public access even for admins via API
-- Use Edge Functions for admin operations
```
### Secret Tables
```sql
-- Consider not exposing at all
REVOKE ALL ON TABLE api_keys FROM anon, authenticated;
-- Or use views that hide sensitive columns
CREATE VIEW public.api_keys_safe AS
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:
1. **Before starting any action** → Log the action to `.sb-pentest-audit.log`
2. **After each table discovered** → Immediately update `.sb-pentest-context.json`
3. **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)
1. **Update `.sb-pentest-context.json`** with results:
```json
{
"tables": {
"count": 8,
"list": [ ... ],
"by_risk": { "high": [], "medium": [], "low": [] }
}
}
```
2. **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
```
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/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 |
### Evidence Format
```json
{
"evidence_id": "API-TBL-001",
"timestamp": "2025-01-31T10:15:00Z",
"category": "api-audit",
"type": "table_enumeration",
"request": {
"method": "GET",
"url": "https://abc123def.supabase.co/rest/v1/",
"headers": {
"apikey": "[REDACTED]"
},
"curl_command": "curl -s 'https://abc123def.supabase.co/rest/v1/' -H 'apikey: $ANON_KEY'"
},
"tables_found": [
{
"name": "users",
"schema": "public",
"columns": ["id", "email", "name", "created_at"],
"primary_key": "id",
"risk_level": "high",
"risk_reason": "Contains PII"
},
{
"name": "orders",
"schema": "public",
"columns": ["id", "user_id", "total", "status"],
"primary_key": "id",
"risk_level": "high",
"risk_reason": "Financial data"
}
],
"summary": {
"total_tables": 8,
"high_risk": 2,
"medium_risk": 3,
"low_risk": 3
}
}
```
### Add to curl-commands.sh
```bash
# === TABLE ENUMERATION ===
# List all exposed tables via OpenAPI schema
curl -s "$SUPABASE_URL/rest/v1/" -H "apikey: $ANON_KEY"
```
## Related Skills
- `supabase-audit-tables-read` — Test actual data access
- `supabase-audit-rls` — Verify RLS policies
- `supabase-audit-rpc` — Check exposed functions
© yoanbernabeu · MIT · yoanbernabeu/supabase-pentest-skills