home / skills / yoanbernabeu / supabase-pentest-skills / supabase-audit-authenticated
This skill creates a test authenticated user to audit access gaps versus anonymous users and detect IDOR, cross-user access, and privilege escalation.
npx playbooks add skill yoanbernabeu/supabase-pentest-skills --skill supabase-audit-authenticatedReview the files below or copy the command above to add this skill to your agents.
---
name: supabase-audit-authenticated
description: Create a test user (with explicit permission) to audit what authenticated users can access vs anonymous users. Detects IDOR, cross-user access, and privilege escalation.
---
# Authenticated User 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 test**
> - 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 creates a test user (with explicit permission) to compare authenticated vs anonymous access and detect IDOR vulnerabilities.
## ā ļø IMPORTANT: User Consent Required
```
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā š USER CREATION CONSENT REQUIRED ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£
ā ā
ā This skill will CREATE A TEST USER in your Supabase project. ā
ā ā
ā The user will be created with: ā
ā ⢠Email: pentest-[random]@security-audit.local ā
ā ⢠Password: Strong random password (32+ chars) ā
ā ⢠Purpose: Testing authenticated access vs anonymous ā
ā ā
ā At the end of the audit, you will be asked if you want to ā
ā DELETE the test user (recommended). ā
ā ā
ā Do you authorize the creation of a test user? ā
ā Type "yes, create test user" to proceed. ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
```
**DO NOT proceed without explicit user consent.**
## When to Use This Skill
- After completing anonymous access tests
- To detect IDOR (Insecure Direct Object Reference) vulnerabilities
- To test cross-user data access
- To verify RLS policies work for authenticated users
- To find privilege escalation issues
## Prerequisites
- Signup must be open (or use invite flow)
- Anon key available
- Anonymous audit completed (recommended)
## Why Authenticated Testing Matters
Many vulnerabilities only appear with authentication:
| Vulnerability | Anonymous | Authenticated |
|---------------|-----------|---------------|
| **RLS bypass (no RLS)** | ā Detectable | ā Detectable |
| **IDOR** | ā Not visible | ā **Only visible** |
| **Cross-user access** | ā Not visible | ā **Only visible** |
| **Privilege escalation** | ā Not visible | ā **Only visible** |
| **Overly permissive RLS** | Partial | ā **Full detection** |
## Test User Creation
### Email Format
```
pentest-[8-char-random]@security-audit.local
```
Example: `[email protected]`
### Password Generation
Strong password with:
- 32+ characters
- Uppercase, lowercase, numbers, symbols
- Cryptographically random
Example: `Xk9$mP2#vL5@nQ8&jR4*wY7!hT3%bU6^`
**The password is displayed ONCE and saved to evidence.**
## Tests Performed
### 1. User Creation & Login
```bash
# Create user
curl -X POST "$SUPABASE_URL/auth/v1/signup" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "[STRONG_PASSWORD]"}'
# Login and get JWT
curl -X POST "$SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "[STRONG_PASSWORD]"}'
```
### 2. Authenticated vs Anonymous Comparison
For each table:
| Test | Anonymous | Authenticated | Finding |
|------|-----------|---------------|---------|
| SELECT | 0 rows | 1,247 rows | š“ Auth-only exposure |
| Own data | N/A | Only own row | ā
RLS working |
| Other users' data | N/A | All rows | š“ Cross-user access |
### 3. IDOR Testing
```bash
# As test user, try to access other user's data
curl "$SUPABASE_URL/rest/v1/orders?user_id=eq.[OTHER_USER_ID]" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [TEST_USER_JWT]"
# If returns data: IDOR vulnerability!
```
### 4. Cross-User Access
```bash
# Get test user's ID from JWT
TEST_USER_ID=$(echo $JWT | jq -r '.sub')
# Try to access data belonging to a different user
curl "$SUPABASE_URL/rest/v1/profiles?id=neq.$TEST_USER_ID" \
-H "Authorization: Bearer [TEST_USER_JWT]"
# If returns other users' profiles: Cross-user access!
```
### 5. Storage with Authentication
```bash
# Test authenticated storage access
curl "$SUPABASE_URL/storage/v1/object/list/documents" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [TEST_USER_JWT]"
# Compare with anonymous results
```
### 6. Realtime with Authentication
```javascript
// Subscribe to table changes as authenticated user
const channel = supabase.channel('test')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'orders'
}, payload => console.log(payload))
.subscribe()
// Does it receive OTHER users' order changes?
```
## Output Format
```
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
AUTHENTICATED USER AUDIT
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Test User Creation
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Status: ā
User created successfully
Test User Details:
āāā Email: [email protected]
āāā User ID: 550e8400-e29b-41d4-a716-446655440099
āāā Password: [Saved to evidence - shown once]
āāā JWT obtained: ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Anonymous vs Authenticated Comparison
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Table: users
āāā Anonymous access: 0 rows
āāā Authenticated access: 1,247 rows ā ALL USERS!
āāā Status: š“ P0 - Data hidden from anon but exposed to any auth user
Table: orders
āāā Anonymous access: 0 rows (blocked)
āāā Authenticated access: 1 row (own orders only)
āāā Status: ā
RLS working correctly
Table: profiles
āāā Anonymous access: 0 rows
āāā Authenticated access: 1,247 rows ā ALL PROFILES!
āāā Own profile only expected: ā NO
āāā Status: š“ P0 - Cross-user profile access
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
IDOR Testing
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Test: Access other user's orders by ID
āāā Request: GET /orders?user_id=eq.[other-user-id]
āāā Auth: Test user JWT
āāā Response: 200 OK - 15 orders returned
āāā Status: š“ P0 - IDOR VULNERABILITY
Proof:
curl "$URL/rest/v1/orders?user_id=eq.other-user-uuid" \
-H "Authorization: Bearer [test-user-jwt]"
# Returns orders belonging to other-user-uuid!
Test: Access admin endpoints
āāā Request: GET /functions/v1/admin-panel
āāā Auth: Test user JWT (regular user)
āāā Response: 200 OK - Admin data returned!
āāā Status: š“ P0 - PRIVILEGE ESCALATION
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Storage with Authentication
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Bucket: documents
āāā Anonymous: ā 0 files (blocked)
āāā Authenticated: ā
523 files visible ā ALL USERS' FILES!
āāā Status: š“ P1 - Auth users see all documents
Bucket: user-uploads
āāā Anonymous: ā 0 files
āāā Authenticated: 3 files (own files only)
āāā Status: ā
RLS working correctly
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Summary
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
New Findings (Auth-only):
āāā š“ P0: users table - all users visible to any auth user
āāā š“ P0: profiles table - cross-user access
āāā š“ P0: IDOR in orders - can access any user's orders
āāā š“ P0: Privilege escalation in admin-panel
āāā š P1: documents bucket - all files visible to auth users
Comparison:
āāā Issues found (Anonymous): 3
āāā Issues found (Authenticated): 8 ā 5 NEW ISSUES!
āāā Auth-only vulnerabilities: 5
Recommendation:
These issues were NOT visible in anonymous testing!
Always test with authenticated users.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Cleanup
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ļø Test user still exists in database.
Do you want to delete the test user?
Email: [email protected]
[This requires service_role key or manual deletion]
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
```
## Context Output
```json
{
"authenticated_audit": {
"timestamp": "2025-01-31T12:00:00Z",
"test_user": {
"email": "[email protected]",
"user_id": "550e8400-e29b-41d4-a716-446655440099",
"created_at": "2025-01-31T12:00:00Z",
"deleted": false
},
"comparison": {
"tables": {
"users": {
"anon_access": 0,
"auth_access": 1247,
"expected_auth_access": "own_row_only",
"severity": "P0",
"finding": "All users visible to any authenticated user"
},
"orders": {
"anon_access": 0,
"auth_access": 1,
"expected_auth_access": "own_rows_only",
"severity": null,
"finding": "RLS working correctly"
}
},
"idor_tests": [
{
"test": "access_other_user_orders",
"vulnerable": true,
"severity": "P0",
"proof": "curl command..."
}
],
"privilege_escalation": [
{
"endpoint": "/functions/v1/admin-panel",
"vulnerable": true,
"severity": "P0"
}
]
},
"summary": {
"anon_issues": 3,
"auth_issues": 8,
"auth_only_issues": 5
}
}
}
```
## RLS Policy Examples
### Correct: Users see only their own data
```sql
-- This RLS policy is correct
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Result:
-- Anonymous: 0 rows
-- Authenticated: 1 row (own data)
```
### Incorrect: All authenticated users see everything
```sql
-- This RLS policy is WRONG
CREATE POLICY "Authenticated users see all"
ON users FOR SELECT
USING (auth.role() = 'authenticated'); -- ā Too permissive!
-- Result:
-- Anonymous: 0 rows
-- Authenticated: ALL rows ā VULNERABILITY!
```
### Correct fix:
```sql
-- Fix: Add user ownership check
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id); -- ā
Only own row
```
## Cleanup Options
### Option 1: Manual deletion (Dashboard)
```
Supabase Dashboard ā Authentication ā Users ā Find test user ā Delete
```
### Option 2: Via service_role key (if available)
```bash
curl -X DELETE "$SUPABASE_URL/auth/v1/admin/users/[USER_ID]" \
-H "apikey: $SERVICE_ROLE_KEY" \
-H "Authorization: Bearer $SERVICE_ROLE_KEY"
```
### Option 3: Leave for later
The test user uses a non-functional email domain (`security-audit.local`) and cannot be used maliciously.
## MANDATORY: Evidence Collection
š **Evidence Directory:** `.sb-pentest-evidence/05-auth-audit/authenticated-tests/`
### Evidence Files to Create
| File | Content |
|------|---------|
| `test-user-created.json` | Test user details (password saved securely) |
| `anon-vs-auth-comparison.json` | Side-by-side comparison |
| `idor-tests/[table].json` | IDOR test results |
| `privilege-escalation.json` | Privilege escalation tests |
### Evidence Format
```json
{
"evidence_id": "AUTH-TEST-001",
"timestamp": "2025-01-31T12:00:00Z",
"category": "auth-audit",
"type": "authenticated_testing",
"test_user": {
"email": "[email protected]",
"user_id": "550e8400-...",
"password": "[STORED SECURELY - DO NOT COMMIT]"
},
"comparison_test": {
"table": "users",
"anonymous": {
"curl_command": "curl '$URL/rest/v1/users' -H 'apikey: $ANON_KEY'",
"response_status": 200,
"rows_returned": 0
},
"authenticated": {
"curl_command": "curl '$URL/rest/v1/users' -H 'apikey: $ANON_KEY' -H 'Authorization: Bearer $JWT'",
"response_status": 200,
"rows_returned": 1247
},
"finding": {
"severity": "P0",
"issue": "All users visible to any authenticated user",
"expected": "Only own row should be visible",
"impact": "Full user enumeration for any authenticated user"
}
}
}
```
### Add to curl-commands.sh
```bash
# === AUTHENTICATED TESTING ===
# NOTE: Replace [JWT] with test user's JWT
# Compare anonymous vs authenticated access
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" -H "apikey: $ANON_KEY"
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" -H "apikey: $ANON_KEY" -H "Authorization: Bearer [JWT]"
# IDOR test - access other user's data
curl -s "$SUPABASE_URL/rest/v1/orders?user_id=eq.[OTHER_USER_ID]" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [JWT]"
# Cross-user profile access
curl -s "$SUPABASE_URL/rest/v1/profiles?id=neq.[TEST_USER_ID]" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [JWT]"
```
## 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 user creation** ā Log consent and action to `.sb-pentest-audit.log`
2. **After user created** ā Immediately save user details to context and evidence
3. **After each comparison test** ā Update `.sb-pentest-context.json` with results
4. **After each IDOR test** ā Save evidence immediately
This ensures that if the skill is interrupted, crashes, or times out, all findings up to that point are preserved.
### Required Actions (Progressive)
1. **Log user creation:**
```
[TIMESTAMP] [supabase-audit-authenticated] [CONSENT] User authorized test user creation
[TIMESTAMP] [supabase-audit-authenticated] [CREATED] Test user [email protected]
```
2. **Save test user to context immediately:**
```json
{
"authenticated_audit": {
"test_user": {
"email": "...",
"user_id": "...",
"created_at": "..."
}
}
}
```
3. **Log each finding as discovered:**
```
[TIMESTAMP] [supabase-audit-authenticated] [FINDING] P0: IDOR in orders table
```
**FAILURE TO UPDATE CONTEXT FILES PROGRESSIVELY IS NOT ACCEPTABLE.**
## Related Skills
- `supabase-audit-auth-signup` ā Test if signup is open first
- `supabase-audit-tables-read` ā Compare with anonymous results
- `supabase-audit-rls` ā Deep dive into RLS policies
- `supabase-audit-functions` ā Test function access with auth
- `supabase-report` ā Include auth-only findings in report
This skill creates a disposable test user with explicit consent and runs authenticated-vs-anonymous checks to detect IDOR, cross-user access, and privilege escalation in Supabase projects. It captures evidence and saves progressive context and logs as each test completes. The goal is to reveal vulnerabilities that only appear for authenticated users and produce actionable findings and remediation guidance.
After explicit authorization, the skill generates a strong random password and a pentest-[8char] email, signs up the test user via the Supabase auth endpoint, and obtains a JWT. It then performs table, storage, realtime, and function checks as both anonymous and authenticated users, comparing results to identify auth-only issues like IDOR or overly permissive RLS. All actions, responses, and evidence files are written incrementally to audit logs and context files so results are preserved if the run is interrupted.
Do I need to approve creating a test user?
Yes. This skill requires explicit consent before creating the test account; it will not proceed without your authorization.
What happens if the audit is interrupted?
All critical outputs and findings are written progressively to the audit log and context files so partial results remain available.