โ† Back to skills
extension
Category: Development & EngineeringNo API key required

supabase-audit-rls

Test Row Level Security (RLS) policies for common bypass vulnerabilities and misconfigurations.

personAuthor: jakexiaohubgithub

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:

-- 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:

  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:

    {
      "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)

{
  "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