supabase-audit-rls — Claude Code skill: audit Supabase RLS
Tests Row Level Security policies in Supabase for bypasses and misconfigurations. Useful once tables start leaking data.
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rls -a claude-codeWhat supabase-audit-rls does
The skill runs your Supabase tables through a set of test vectors: unauthenticated access, reading another user's rows with a different token, filter bypass via OR conditions, leakage through related tables, and RPC calls that skip policies. For each table you get whether RLS is on, which policies exist, and what they actually allow.
Findings land in .sb-pentest-context.json the moment they are found, and the test log goes to .sb-pentest-audit.log before and after every check. That matters because a crash or an interrupt still leaves the earlier results on disk. The report grades issues: P0 for a table with no RLS at all, P1 for smaller holes like a SELECT policy with no INSERT, or a mismatch between USING and WITH CHECK.
You need the project's anon key and a list of tables before starting. Testing with an authenticated user token gives more coverage — without it the cross-user scenarios stay partial. The skill hits a live database with real requests, so point it at a copy or run it when writes to those tables are not critical.
What it helps with
- find Supabase tables left open without RLS
- check whether the anon key reads other people's rows
- catch filter bypass through OR conditions in REST queries
- spot data leakage via related tables and RPC functions
- sort findings into P0 and P1 priorities
Example prompts for the agent
- Audit RLS policies on my Supabase project
- Test RLS on the users table
- Test RLS policies using this user token: eyJ...
How to install supabase-audit-rls
1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rls -a claude-code2Manually: put the files in place
~/.claude/skills/supabase-audit-rls/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/supabase-audit-rls/
1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rls -a codex2Manually: put the files in place
.agents/skills/supabase-audit-rls/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rls -a cursor2Manually: put the files in place
.cursor/skills/supabase-audit-rls/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install yoanbernabeu/supabase-pentest-skills supabase-audit-rls2Manually: put the files in place
.github/skills/supabase-audit-rls/SKILL.md1Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rls -a gemini-cli2Platform CLI
gemini skills install https://github.com/yoanbernabeu/supabase-pentest-skills --path skills/audit-api/supabase-audit-rlsManually: put the files in place
.devin/skills/supabase-audit-rls/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rls -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-rls
How do I install supabase-audit-rls in Claude?
Run npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rls -a claude-code. The skill file lands at ~/.claude/skills/supabase-audit-rls/SKILL.md.
What does supabase-audit-rls do?
It tests Supabase Row Level Security policies against common bypass techniques and misconfigurations. You get a per-table report with priorities and ready SQL fixes.
What do I need before running it?
A list of tables and the project's anon key. Cross-user access tests also need an authenticated user token.
Source text
SKILL.md · MIT
---
name: supabase-audit-rls
description: Test Row Level Security (RLS) policies for common bypass vulnerabilities and misconfigurations.
---
# RLS Policy 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 finding**
> - 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 tests Row Level Security (RLS) policies for common vulnerabilities and misconfigurations.
## When to Use This Skill
- After discovering data exposure in tables
- To verify RLS policies are correctly implemented
- To test for common RLS bypass techniques
- As part of a comprehensive security audit
## Prerequisites
- Tables listed
- Anon key available
- Preferably also test with an authenticated user token
## Understanding RLS
Row Level Security in Supabase/PostgreSQL:
```sql
-- Enable RLS on a table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Create a policy
CREATE POLICY "Users see own posts"
ON posts FOR SELECT
USING (auth.uid() = author_id);
```
**If RLS is enabled but no policies exist, ALL access is blocked.**
## Common RLS Issues
| Issue | Description | Severity |
|-------|-------------|----------|
| RLS Disabled | Table has no RLS protection | P0 |
| Missing Policy | RLS enabled but no SELECT policy | Variable |
| Overly Permissive | Policy allows too much access | P0-P1 |
| Missing Operation | SELECT policy but no INSERT/UPDATE/DELETE | P1 |
| USING vs WITH CHECK | Read allowed but write inconsistent | P1 |
## Test Vectors
The skill tests these common bypass scenarios:
### 1. Unauthenticated Access
```
GET /rest/v1/users?select=*
# No Authorization header or with anon key only
```
### 2. Cross-User Access
```
# As user A, try to access user B's data
GET /rest/v1/orders?user_id=eq.[user-b-id]
Authorization: Bearer [user-a-token]
```
### 3. Filter Bypass
```
# Try to bypass filters with OR conditions
GET /rest/v1/posts?or=(published.eq.true,published.eq.false)
```
### 4. Join Exploitation
```
# Try to access data through related tables
GET /rest/v1/comments?select=*,posts(*)
```
### 5. RPC Bypass
```
# Check if RPC functions bypass RLS
POST /rest/v1/rpc/get_all_users
```
## Usage
### Basic RLS Audit
```
Audit RLS policies on my Supabase project
```
### Specific Table
```
Test RLS on the users table
```
### With Authenticated User
```
Test RLS policies using this user token: eyJ...
```
## Output Format
```
═══════════════════════════════════════════════════════════
RLS POLICY AUDIT
═══════════════════════════════════════════════════════════
Project: abc123def.supabase.co
Tables Audited: 8
─────────────────────────────────────────────────────────
RLS Status by Table
─────────────────────────────────────────────────────────
1. users
RLS Enabled: ❌ NO
Status: 🔴 P0 - NO RLS PROTECTION
All operations allowed without restriction!
Test Results:
├── Anon SELECT: ✓ Returns all 1,247 rows
├── Anon INSERT: ✓ Succeeds (tested with rollback)
├── Anon UPDATE: ✓ Would succeed
└── Anon DELETE: ✓ Would succeed
Immediate Fix:
```sql
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see own data"
ON users FOR ALL
USING (auth.uid() = id);
```
2. posts
RLS Enabled: ✅ YES
Policies Found: 2
Status: ✅ PROPERLY CONFIGURED
Policies:
├── "Public sees published" (SELECT)
│ └── USING: (published = true)
└── "Authors manage own" (ALL)
└── USING: (auth.uid() = author_id)
Test Results:
├── Anon SELECT: Only published posts (correct)
├── Anon INSERT: ❌ Blocked (correct)
├── Cross-user access: ❌ Blocked (correct)
└── Filter bypass: ❌ Blocked (correct)
3. orders
RLS Enabled: ✅ YES
Policies Found: 1
Status: 🟠 P1 - PARTIAL ISSUE
Policies:
└── "Users see own orders" (SELECT)
└── USING: (auth.uid() = user_id)
Issue Found:
├── No INSERT policy - users can't create orders via API
├── No UPDATE policy - users can't modify their orders
└── This may be intentional (orders via Edge Functions)
Recommendation: Document if intentional, or add policies:
```sql
CREATE POLICY "Users insert own orders"
ON orders FOR INSERT
WITH CHECK (auth.uid() = user_id);
```
4. comments
RLS Enabled: ✅ YES
Policies Found: 2
Status: 🟠 P1 - BYPASS POSSIBLE
Policies:
├── "Anyone can read" (SELECT)
│ └── USING: (true) ← Too permissive
└── "Users comment on posts" (INSERT)
└── WITH CHECK: (auth.uid() = user_id)
Issue Found:
└── SELECT policy allows reading all comments
including user_id, enabling user correlation
Recommendation:
```sql
-- Use a view to hide user_id
CREATE VIEW public.comments_public AS
SELECT id, post_id, content, created_at FROM comments;
```
5. settings
RLS Enabled: ❌ NO
Status: 🔴 P0 - NO RLS PROTECTION
Contains sensitive configuration!
Immediate action required.
─────────────────────────────────────────────────────────
Summary
─────────────────────────────────────────────────────────
RLS Disabled: 2 tables (users, settings) ← CRITICAL
RLS Enabled: 6 tables
├── Properly Configured: 3
├── Partial Issues: 2
└── Major Issues: 1
Bypass Tests:
├── Unauthenticated access: 2 tables vulnerable
├── Cross-user access: 0 tables vulnerable
├── Filter bypass: 0 tables vulnerable
└── Join exploitation: 1 table allows data leakage
═══════════════════════════════════════════════════════════
```
## Context Output
```json
{
"rls_audit": {
"timestamp": "2025-01-31T10:45:00Z",
"tables_audited": 8,
"summary": {
"rls_disabled": 2,
"rls_enabled": 6,
"properly_configured": 3,
"partial_issues": 2,
"major_issues": 1
},
"findings": [
{
"table": "users",
"rls_enabled": false,
"severity": "P0",
"issue": "No RLS protection",
"operations_exposed": ["SELECT", "INSERT", "UPDATE", "DELETE"]
},
{
"table": "comments",
"rls_enabled": true,
"severity": "P1",
"issue": "Overly permissive SELECT policy",
"detail": "user_id exposed enabling correlation"
}
]
}
}
```
## Common RLS Patterns
### Good: User owns their data
```sql
CREATE POLICY "Users own their data"
ON user_data FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
```
### Good: Public read, authenticated write
```sql
-- Anyone can read
CREATE POLICY "Public read" ON posts
FOR SELECT USING (published = true);
-- Only authors can write
CREATE POLICY "Author write" ON posts
FOR INSERT WITH CHECK (auth.uid() = author_id);
CREATE POLICY "Author update" ON posts
FOR UPDATE USING (auth.uid() = author_id);
```
### Bad: Using (true)
```sql
-- ❌ Too permissive
CREATE POLICY "Anyone" ON secrets
FOR SELECT USING (true);
```
### Bad: Forgetting WITH CHECK
```sql
-- ❌ Users can INSERT any user_id
CREATE POLICY "Insert" ON posts
FOR INSERT WITH CHECK (true); -- Should check user_id!
```
## RLS Bypass Documentation
For each bypass found, the skill provides:
1. **Description** of the vulnerability
2. **Proof of concept** query
3. **Impact** assessment
4. **Fix** with SQL code
5. **Documentation** link
## 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 RLS finding** → Immediately update `.sb-pentest-context.json`
3. **After each test completes** → Log the result 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
{
"rls_audit": {
"timestamp": "...",
"tables_audited": 8,
"summary": { "rls_disabled": 2, ... },
"findings": [ ... ]
}
}
```
2. **Log to `.sb-pentest-audit.log`**:
```
[TIMESTAMP] [supabase-audit-rls] [START] Auditing RLS policies
[TIMESTAMP] [supabase-audit-rls] [FINDING] P0: users table has no RLS
[TIMESTAMP] [supabase-audit-rls] [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/rls-tests/`
### Evidence Files to Create
| File | Content |
|------|---------|
| `rls-tests/[table]-anon.json` | Anonymous access test results |
| `rls-tests/[table]-auth.json` | Authenticated access test results |
| `rls-tests/cross-user-test.json` | Cross-user access attempts |
### Evidence Format (RLS Bypass)
```json
{
"evidence_id": "RLS-001",
"timestamp": "2025-01-31T10:25:00Z",
"category": "api-audit",
"type": "rls_test",
"severity": "P0",
"table": "users",
"rls_enabled": false,
"tests": [
{
"test_name": "anon_select",
"description": "Anonymous user SELECT access",
"request": {
"curl_command": "curl -s '$URL/rest/v1/users?select=*&limit=5' -H 'apikey: $ANON_KEY'"
},
"response": {
"status": 200,
"rows_returned": 5,
"total_accessible": 1247
},
"result": "VULNERABLE",
"impact": "All user data accessible without authentication"
},
{
"test_name": "anon_insert",
"description": "Anonymous user INSERT access",
"request": {
"curl_command": "curl -X POST '$URL/rest/v1/users' -H 'apikey: $ANON_KEY' -d '{...}'"
},
"response": {
"status": 201
},
"result": "VULNERABLE",
"impact": "Can create arbitrary user records"
}
],
"remediation_sql": "ALTER TABLE users ENABLE ROW LEVEL SECURITY;\nCREATE POLICY \"Users see own data\" ON users FOR SELECT USING (auth.uid() = id);"
}
```
### Add to curl-commands.sh
```bash
# === RLS BYPASS TESTS ===
# Test anon access to users table
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY"
# Test filter bypass
curl -s "$SUPABASE_URL/rest/v1/posts?or=(published.eq.true,published.eq.false)" \
-H "apikey: $ANON_KEY"
```
## Related Skills
- `supabase-audit-tables-list` — List tables first
- `supabase-audit-tables-read` — See actual data exposure
- `supabase-audit-rpc` — RPC functions can bypass RLS
- `supabase-report` — Full security report
© yoanbernabeu · MIT · yoanbernabeu/supabase-pentest-skills