active

Supabase RLS Auditor

Safe
System VerifiedSafe

Audit Supabase Row Level Security policies for correctness and security: checks for missing RLS, unrestricted USING (true), missing WITH CHECK, anon access leaks, service-role bypasses, and auth.uid() typos that silently lock out all users.

@api/supabase-rls-auditor

supabase
database
security
rls
postgresql
audit

Supabase RLS Auditor

Purpose: Audit a Supabase database migration file or SQL containing Row Level Security (RLS) policies for correctness and security. Catches the mistakes that silently either expose all data or lock out all users. Every Supabase table touched by users should pass this audit before going to production.


Invocation

code
/rls-audit <migration-file-or-sql>

The 8-Point RLS Checklist

Check 1: RLS Enabled on Every Table

Every table that stores user data must have:

sql
ALTER TABLE [table_name] ENABLE ROW LEVEL SECURITY;

Failure: Table has no RLS — all authenticated users can read/write all rows.


Check 2: No Unrestricted USING (true) on Sensitive Tables

sql
-- ❌ BAD — exposes ALL rows to ALL authenticated users
CREATE POLICY "all_read" ON users FOR SELECT USING (true);

-- ✅ GOOD — only user's own rows
CREATE POLICY "own_rows" ON users FOR SELECT USING (auth.uid() = user_id);

Exception: Public tables (e.g., skills with status = 'active') may use USING (true) for SELECT only.


Check 3: INSERT Policies Have WITH CHECK

sql
-- ❌ BAD — INSERT policy with no WITH CHECK
CREATE POLICY "insert_tip" ON tips FOR INSERT TO authenticated USING (true);

-- ✅ GOOD
CREATE POLICY "insert_tip" ON tips FOR INSERT TO authenticated
  WITH CHECK (auth.uid() = tipper_id);

Check 4: No Anon Role Access to Private Tables

Policies with TO anon on tables containing user data are dangerous. Flag any policy where:

  • Role is anon or omitted (defaults to all roles including anon)
  • Table contains PII (email, phone, address) or financial data

Check 5: Service Role Bypasses Are Documented

sql
-- This bypasses ALL RLS — it should be intentional and documented
CREATE POLICY "service_role_all" ON [table]
  USING (auth.role() = 'service_role');

Flag: every service-role bypass needs a comment explaining WHY it's needed.


Check 6: auth.uid() Typo Check

Common typo that silently evaluates to NULL = user_id (always false — locks out all users):

sql
-- ❌ BAD TYPOS
USING (auth.uid = user_id)         -- missing () → NULL
USING (auth.uid() = userId)        -- camelCase column name
USING (auth_uid() = user_id)       -- underscore instead of dot

-- ✅ CORRECT
USING (auth.uid() = user_id)

Check 7: Separate Policies per Operation

sql
-- ❌ BAD — FOR ALL lets users UPDATE and DELETE other users' data if USING passes
CREATE POLICY "user_access" ON orders FOR ALL USING (auth.uid() = user_id);

-- ✅ GOOD — explicit per-operation policies
CREATE POLICY "select_own" ON orders FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "insert_own" ON orders FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "update_own" ON orders FOR UPDATE USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

Check 8: Recursive Policy Risk

Policies that query the same table they protect can cause infinite recursion. Flag any policy that contains a subquery on the same table.


Output Format

code
## RLS Audit: [migration file name]

### Tables Audited: N

---

#### [table_name]

| Check | Status | Notes |
|-------|--------|-------|
| RLS enabled | ✅ / ❌ FAIL | |
| No unrestricted USING | ✅ / ⚠️ REVIEW | |
| INSERT has WITH CHECK | ✅ / ❌ FAIL | |
| No anon leaks | ✅ / ❌ FAIL | |
| Service bypasses documented | ✅ / ❌ FAIL | |
| auth.uid() correct | ✅ / ❌ FAIL | |
| Per-operation policies | ✅ / ⚠️ REVIEW | |
| No recursion risk | ✅ / ❌ FAIL | |

**[table_name] verdict:** PASS / FAIL / NEEDS REVIEW

---

### Overall Audit Result: PASS / FAIL

Critical issues (must fix before production): [N]
Warnings (review before production): [N]

### Fix SQL

[SQL to fix any FAIL items]

Rules

  • Check 1 (RLS enabled) failure is always CRITICAL — no exceptions
  • Check 2 (USING true) on private tables is always CRITICAL
  • Flag any policy targeting FOR ALL — it's almost always wrong
  • If a table has no policies at all (but RLS is enabled): that's a lockout — all inserts/selects will fail silently
Dormant$0/mo

$20 more to next tier

Info

Created February 20, 2026
Version 1.0.0
User-invoked
Terminal output

Embed

Add this skill card to any webpage.

<iframe src="https://skillslap.com/skill/e95dd9f5-5389-4add-b1af-a6cc5bc4eb68/embed"
        width="400" height="200"
        style="border:none;border-radius:12px;"
        title="SkillSlap Skill: Supabase RLS Auditor">
</iframe>