
Supabase Audit Tables Read
- 322 installs
- 60 repo stars
- Updated January 31, 2026
- yoanbernabeu/supabase-pentest-skills
supabase-audit-tables-read is a Supabase pentest agent skill that performs read-only PostgREST SELECT probes on exposed tables to verify Row Level Security and document actual data exposure for authorized security audito
About
supabase-audit-tables-read is a read-only Supabase API audit skill from the 24-skill supabase-pentest-skills toolkit that attempts GET /rest/v1/{table}?select=* queries using an anon key after tables are listed. It supports Quick (5 rows), Sample (10 random rows), and Count (HEAD-only) modes, classifies findings as P0–P2, and progressively updates .sb-pentest-context.json and .sb-pentest-audit.log after each table tested. Developers reach for supabase-audit-tables-read when RLS policies exist on paper but you need proof of which PII, secrets, or row counts are actually retrievable without authentication. The skill auto-invokes supabase-audit-tables-list when needed and saves redacted JSON evidence under .sb-pentest-evidence/03-api-audit/data-samples/.
- Reads exposed Supabase tables to audit data accessibility
- Identifies misconfigured Row Level Security policies
- Part of a full Supabase pentest skill suite
- Logs findings progressively to context files for resilience
- Compatible with Claude Code, Cursor, and GitHub Copilot
Supabase Audit Tables Read by the numbers
- 322 all-time installs (skills.sh)
- +18 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #612 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yoanbernabeu/supabase-pentest-skills --skill supabase-audit-tables-readAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 322 |
|---|---|
| repo stars | ★ 60 |
| Last updated | January 31, 2026 |
| Repository | yoanbernabeu/supabase-pentest-skills ↗ |
How do you test Supabase RLS table reads?
Tests table data access in Supabase by attempting reads on exposed tables to identify what data is actually accessible to unauthenticated or low-privilege users.
Who is it for?
Backend engineers and security reviewers running authorized read-only Supabase pentests who need evidence-backed proof of PostgREST data exposure.
Skip if: Developers who lack authorization to test the target, need write/delete mutations, or have not yet obtained a Supabase anon key and table list.
When should I use this skill?
An authorized Supabase security audit has listed exposed tables and you must verify what data anon or low-privilege keys can actually SELECT.
What you get
Per-table exposure report with P0–P2 severity, redacted sample JSON, curl reproduction commands, and updated .sb-pentest-context.json data_access results.
- .sb-pentest-context.json data_access block
- Redacted per-table JSON evidence
- P0–P2 exposure summary report
By the numbers
- Belongs to a 24-skill Supabase pentest toolkit
- Supports 3 read test modes: Quick, Sample, and Count
- Uses P0, P1, and P2 severity labels for exposure findings
Files
Table Data Access Test
🔴 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 table tested- Log to .sb-pentest-audit.log BEFORE and AFTER each 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 attempts to read data from exposed tables to determine what information is actually accessible.
When to Use This Skill
- After listing tables, to verify actual access
- To test RLS policy effectiveness
- To assess the severity of data exposure
- To document exactly what data can be retrieved
Prerequisites
- Tables listed (auto-invokes
supabase-audit-tables-listif needed) - Anon key available
How It Works
The skill performs SELECT queries on each exposed table:
GET https://[project].supabase.co/rest/v1/[table]?select=*&limit=5
Authorization: Bearer [anon-key]Important: This is READ-ONLY. No data is modified or deleted.
Test Modes
| Mode | Description | Queries |
|---|---|---|
| Quick | First 5 rows from each table | ?limit=5 |
| Sample | Random sample across tables | ?limit=10&order=random |
| Count | Just row counts, no data | HEAD request |
Usage
Basic Read Test
Test read access on exposed tablesQuick Count Only
Count accessible rows in all tables (no data retrieval)Specific Table
Test read access on the users tableOutput Format
═══════════════════════════════════════════════════════════
DATA ACCESS TEST RESULTS
═══════════════════════════════════════════════════════════
Test Mode: Quick (5 rows per table)
Tables Tested: 8
─────────────────────────────────────────────────────────
Results by Table
─────────────────────────────────────────────────────────
1. users
Status: 🔴 P0 - DATA EXPOSED
Rows Retrieved: 5 (of 1,247 total)
Sample Data:
┌─────────────────────────────────────────────────────┐
│ id: 550e8400-e29b-41d4-a716-446655440001 │
│ email: john.doe@example.com ← PII EXPOSED │
│ name: John Doe ← PII EXPOSED │
│ avatar_url: https://... │
│ created_at: 2025-01-15T10:30:00Z │
└─────────────────────────────────────────────────────┘
Finding: User emails and names accessible without auth
2. profiles
Status: 🟠 P1 - PARTIAL ACCESS
Rows Retrieved: 5
Note: Only public fields returned (RLS working partially)
Columns Visible: id, bio, website
Columns Blocked: user_id, social_links, private_notes
3. posts
Status: ✅ EXPECTED ACCESS
Rows Retrieved: 5
Note: Only published=true posts returned (RLS working)
Data: Public content, appropriate access level
4. orders
Status: ✅ BLOCKED
Response: 403 Forbidden
Message: "new row violates row-level security policy"
Note: RLS properly blocking access
5. api_keys
Status: ✅ BLOCKED
Response: 403 Forbidden
Note: RLS properly protecting secrets
6. products
Status: ✅ EXPECTED ACCESS
Rows Retrieved: 5
Note: Public catalog data, appropriate access
7. comments
Status: 🟠 P1 - MORE DATA THAN EXPECTED
Rows Retrieved: 5
Issue: user_id column exposed (can correlate to users)
Recommendation: Use a view to hide user_id
8. settings
Status: 🔴 P0 - SENSITIVE DATA EXPOSED
Rows Retrieved: 3
Sample Data:
┌─────────────────────────────────────────────────────┐
│ key: stripe_webhook_secret │
│ value: whsec_xxxxxxxxxxxx ← SECRET EXPOSED │
└─────────────────────────────────────────────────────┘
Finding: Application secrets in accessible table!
─────────────────────────────────────────────────────────
Summary
─────────────────────────────────────────────────────────
P0 (Critical): 2 tables with sensitive data exposed
P1 (High): 2 tables with partial/unexpected exposure
Blocked: 2 tables properly protected
Expected: 2 tables with appropriate public access
Total Rows Accessible: 1,892 across exposed tables
Immediate Actions:
1. Fix 'settings' table - remove from public or add RLS
2. Fix 'users' table - add RLS to protect email/name
3. Review 'comments' to hide user correlation
═══════════════════════════════════════════════════════════Severity Assessment
| Status | Severity | Criteria |
|---|---|---|
| 🔴 DATA EXPOSED | P0 | Sensitive data (PII, secrets, financial) accessible |
| 🟠 PARTIAL ACCESS | P1 | More data than expected, but not critical |
| 🟡 UNEXPECTED | P2 | Accessible but low-risk data |
| ✅ BLOCKED | - | RLS properly preventing access |
| ✅ EXPECTED | - | Public data, appropriate access |
Data Classification
The skill identifies sensitive data types:
| Type | Patterns | Severity if Exposed |
|---|---|---|
| PII | email, phone, name, address | P0 |
| Financial | amount, total, card, payment | P0 |
| Secrets | key, secret, token, password | P0 |
| Auth | user_id, session, jwt | P1 |
| Metadata | created_at, updated_at | P2 |
Context Output
{
"data_access": {
"timestamp": "2025-01-31T10:30:00Z",
"tables_tested": 8,
"summary": {
"p0_exposed": 2,
"p1_partial": 2,
"blocked": 2,
"expected": 2
},
"results": [
{
"table": "users",
"status": "exposed",
"severity": "P0",
"rows_accessible": 1247,
"sensitive_columns": ["email", "name"],
"sample_redacted": true
},
{
"table": "settings",
"status": "exposed",
"severity": "P0",
"rows_accessible": 3,
"sensitive_data_types": ["secrets"],
"finding": "Application secrets exposed"
}
],
"total_rows_accessible": 1892
}
}Audit Log Entry
[2025-01-31T10:30:00Z] READ_TEST_START tables=8
[2025-01-31T10:30:01Z] READ_TEST table=users status=200 rows=5 severity=P0
[2025-01-31T10:30:01Z] READ_TEST table=orders status=403 severity=none
[2025-01-31T10:30:02Z] READ_TEST_COMPLETE exposed=4 blocked=2Remediation Examples
For User Tables
-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Only authenticated users see their own data
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Or create a public view with limited columns
CREATE VIEW public.users_public AS
SELECT id, avatar_url, created_at FROM users;For Settings Tables
-- Remove from public access entirely
REVOKE ALL ON TABLE settings FROM anon, authenticated;
-- Access only via Edge Functions
-- In your Edge Function:
const { data } = await supabaseAdmin
.from('settings')
.select('*')
.eq('key', 'stripe_webhook_secret')
.single()For Content Tables
-- RLS for published content only
CREATE POLICY "Public sees published posts"
ON posts FOR SELECT
USING (published = true);
-- Authors see their own drafts
CREATE POLICY "Authors see own posts"
ON posts FOR SELECT
USING (auth.uid() = author_id);Common Issues
❌ Problem: All tables return 403 ✅ Solution: RLS may be too restrictive or anon key invalid. This is actually good from a security standpoint.
❌ Problem: Empty results but no error ✅ Solution: RLS is filtering all rows. Table structure is exposed but no data.
❌ Problem: Timeout on large tables ✅ Solution: Use count mode or reduce limit.
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 table → Log the action to .sb-pentest-audit.log 2. After each table tested → Immediately update .sb-pentest-context.json with results 3. After each finding → Log the severity 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:
{
"data_access": {
"timestamp": "...",
"tables_tested": 8,
"summary": { "p0_exposed": 2, ... },
"results": [ ... ],
"total_rows_accessible": 1892
}
}2. Log to `.sb-pentest-audit.log`:
[TIMESTAMP] [supabase-audit-tables-read] [START] Testing data access
[TIMESTAMP] [supabase-audit-tables-read] [FINDING] P0: users table exposed
[TIMESTAMP] [supabase-audit-tables-read] [CONTEXT_UPDATED] .sb-pentest-context.json updated3. 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/data-samples/
Evidence Files to Create
| File | Content |
|---|---|
data-samples/[table]-sample.json | Sample data from each accessible table |
data-samples/[table]-blocked.json | Proof of blocked access (403 response) |
Evidence Format (Data Exposed)
{
"evidence_id": "API-READ-001",
"timestamp": "2025-01-31T10:20:00Z",
"category": "api-audit",
"type": "data_access",
"severity": "P0",
"finding_id": "P0-002",
"table": "users",
"request": {
"method": "GET",
"url": "https://abc123def.supabase.co/rest/v1/users?select=*&limit=5",
"headers": {
"apikey": "[REDACTED]",
"Authorization": "Bearer [REDACTED]"
},
"curl_command": "curl -s 'https://abc123def.supabase.co/rest/v1/users?select=*&limit=5' -H 'apikey: $ANON_KEY' -H 'Authorization: Bearer $ANON_KEY'"
},
"response": {
"status": 200,
"headers": {
"content-range": "0-4/1247"
},
"total_rows": 1247,
"sample_data": [
{
"id": "550e8400-e29b-41d4-...",
"email": "[REDACTED]@example.com",
"name": "[REDACTED]",
"created_at": "2025-01-15T10:30:00Z"
}
],
"data_redacted": true
},
"analysis": {
"severity": "P0",
"pii_exposed": ["email", "name"],
"total_records_accessible": 1247,
"authentication_required": false
}
}Evidence Format (Properly Blocked)
{
"evidence_id": "API-READ-002",
"timestamp": "2025-01-31T10:21:00Z",
"table": "orders",
"severity": null,
"response": {
"status": 403,
"body": {"message": "new row violates row-level security policy"}
},
"analysis": {
"rls_working": true,
"access_blocked": true
}
}Add to curl-commands.sh
# === DATA ACCESS TESTS ===
# Test: Users table access
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
# Test: Orders table access (should be blocked)
curl -s "$SUPABASE_URL/rest/v1/orders?select=*&limit=5" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"Related Skills
supabase-audit-tables-list— List tables firstsupabase-audit-rls— Deep dive into RLS policiessupabase-report— Generate full report
Related skills
How it compares
Pick supabase-audit-tables-read over policy-only RLS review when you need live proof of retrievable rows, not just SQL policy definitions.
FAQ
Does supabase-audit-tables-read modify database data?
supabase-audit-tables-read performs read-only GET requests against PostgREST endpoints such as /rest/v1/{table}?select=*&limit=5. The skill never issues INSERT, UPDATE, or DELETE operations and is intended only for authorized internal security assessments.
What files does supabase-audit-tables-read update?
supabase-audit-tables-read progressively updates .sb-pentest-context.json with data_access results, appends timestamped entries to .sb-pentest-audit.log, and stores redacted per-table JSON under .sb-pentest-evidence/03-api-audit/data-samples/ so interrupted runs retain findings.
What prerequisites does supabase-audit-tables-read need?
supabase-audit-tables-read requires an available Supabase anon key and a prior table list from supabase-audit-tables-list, which the skill can auto-invoke. Without those inputs it cannot issue meaningful PostgREST read probes.