home / skills / yoanbernabeu / supabase-pentest-skills / supabase-audit-rls
This skill audits Supabase RLS policies to identify bypass risks and misconfigurations, enabling rapid remediation and stronger data protection.
npx playbooks add skill yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rlsReview the files below or copy the command above to add this skill to your agents.
---
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
This skill tests Row Level Security (RLS) policies in Supabase/PostgreSQL projects to find common bypasses and misconfigurations. It runs automated probes for unauthenticated access, cross-user access, filter and join bypasses, and RPC-based escalation, then produces actionable findings and remediation SQL. The output includes structured findings, severity, proof-of-concept requests, and concrete fixes.
The skill inspects each target table to determine whether RLS is enabled and enumerates existing policies. It executes a set of test vectors (anon queries, authenticated user queries, filter/OR tampering, join and RPC attempts) and records responses to identify exposed operations or overly permissive policies. For each finding it generates a concise impact assessment, proof-of-concept request, recommended SQL remediation, and evidence artifacts.
Do I need an anon key and user token?
You should test with the anon key for unauthenticated behavior and at least one authenticated user token to check cross-user and owner-only policies.
What types of proofs are produced?
Each positive finding includes a proof-of-concept curl command, request/response summary, rows returned, severity, and recommended remediation SQL.