Supabase Multi-Tenant Data Leak — User Accessing Another Tenant's Data
The Exact Error
// CRITICAL INCIDENT — Production data exposure
// Discovered via user report: "I can see other company's invoices"
// The query that should fail but doesn't:
const { data } = await supabase
.from('invoices')
.select('*')
// Missing: .eq('tenant_id', user.tenant_id)
// RLS policy has a bug — tenant_id check not enforcing
// Affected data: 2,847 invoice records across 15 tenants
// Regulatory exposure: GDPR Article 32 breach notification required
// Time to notify regulator: 72 hours from discoveryWhat's Happening
A user from Company A can query and view records belonging to Company B. The RLS policy has a gap that isn't caught by unit tests.
Root Cause
Supabase RLS policies rely on correct JWT claims and policy syntax that is easy to get wrong under edge cases. Common causes: RLS policy missing on a joined table, tenant_id comparison using wrong type (UUID vs text), or OR condition that accidentally widens access.
Quick Diagnosis Checklist
Run through these steps to confirm this is your issue:
Immediately use Service Role key to audit which users accessed which tenant_ids in the last 24h
Check if the breach is ongoing — temporarily disable the affected table's RLS and re-enable with corrected policy
Identify all users who may have accessed data they shouldn't — required for GDPR breach notification
Check every table that joins to the affected one — multi-hop RLS gaps are common
The Permanent Fix
Implement automated RLS boundary tests in your staging CI/CD pipeline that intentionally try to cross tenant boundaries before every deploy. Never let this reach production again.
Drop-In Package That Permanently Fixes This
Instead of building the fix from scratch, use this production-ready package.
// rls-tester.config.ts
import { RLSTester } from './rls-tester'
const tester = new RLSTester({
supabaseUrl: process.env.SUPABASE_URL!,
serviceKey: process.env.SUPABASE_SERVICE_KEY!,
tenants: ['tenant_a_id', 'tenant_b_id', 'tenant_c_id'],
tables: ['documents', 'invoices', 'user_profiles'],
attacks: ['tenant_escalation', 'null_rls', 'jwt_tampering'],
outputReport: 'compliance-audit-log.json' // hand to auditors
})
// Run in CI before every deploy
await tester.runAll() // throws if any isolation failure found