SKILL.md
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.jsonIMMEDIATELY after each finding
- Log to
.sb-pentest-audit.logBEFORE 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:
-- 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
{
"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
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
-- 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)
-- β Too permissive
CREATE POLICY "Anyone" ON secrets
FOR SELECT USING (true);
Bad: Forgetting WITH CHECK
-- β 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:
- Description of the vulnerability
- Proof of concept query
- Impact assessment
- Fix with SQL code
- 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:
- Before testing each table β Log the action to
.sb-pentest-audit.log
- After each RLS finding β Immediately update
.sb-pentest-context.json
- 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)
-
**Update .sb-pentest-context.json** with results:
{
"rls_audit": {
"timestamp": "...",
"tables_audited": 8,
"summary": { "rls_disabled": 2, ... },
"findings": [ ... ]
}
}
-
**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
-
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)
{
"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
# === 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