Supabase RLS: the 5 mistakes we find in every HR SaaS audit
The false sense of security
Supabase has become the default choice for many French HR startups. And for good reason: managed PostgreSQL, built-in auth, auto-generated API. You can ship an MVP in weeks.
The problem is that this speed creates a false sense of security. "We enabled RLS, so we're secure." No. Enabling RLS is putting a lock on the door. But if the policy allows everyone in, the lock does nothing.
Here are the 5 mistakes we find systematically.
Mistake 1: overly permissive SELECT policy
The most common policy we see:
```sql
CREATE POLICY "Users can read" ON employees
FOR SELECT USING (true);
`
Every authenticated user can read every employee. In a multi-tenant SaaS, company A can see company B's employees.
The fix:
```sql
CREATE POLICY "Users can read own org" ON employees
FOR SELECT USING (org_id = auth.jwt() ->> 'org_id');
`
Mistake 2: forgetting policies on related tables
You've secured the employees table, but not payslips. An attacker can list all payslips by querying the payslips table directly, even without access to the employees table.
Every table with sensitive data needs its own policies. And joins must be verified: if payslips has an employee_id column, the policy must check that the employee belongs to the current user's organization.
Mistake 3: service_role key in the frontend
We still see this regularly: the Supabase service_role key hardcoded in frontend JavaScript. This key bypasses all RLS policies. Anyone inspecting the source code can extract it and access the entire database.
The service_role key must never leave your server. The frontend uses the anon key — that's what RLS policies are for.
Mistake 4: no policies on INSERT and UPDATE
Many teams add policies on SELECT but forget INSERT and UPDATE. Result: an authenticated user can create an employee in any organization, or modify someone else's salary.
Every operation (SELECT, INSERT, UPDATE, DELETE) needs its own policy. And UPDATE policies must check both existing rows (USING) and new values (WITH CHECK).
Mistake 5: unsecured RPC functions
Supabase lets you create PostgreSQL functions exposed via API (RPC). These often run with the creator's permissions (SECURITY DEFINER), which bypasses RLS.
If your RPC function does SELECT * FROM employees WHERE id = $1 without checking the organization, a user can access any employee by passing the ID directly.
Solution: use SECURITY INVOKER when possible, or add organization checks within the function itself.
How to verify your policies hold
The simplest test: create two user accounts in two different organizations. Log in as user A and try to access B's data. Not just through the UI — directly via the Supabase API with curl or Postman.
That's exactly what we do in a Supabase audit at CleanIssue. We test every table, every RPC endpoint, every storage bucket. And we document precisely what leaks and how to fix it.
Related articles
Three adjacent analyses to keep exploring the same attack surface.
Supabase RLS: 5 configuration mistakes we find every week
Supabase Row Level Security policies are your first line of defense. Here are the 5 most common mistakes.
RLS mistakes: the 2026 guide for Supabase, PostgreSQL, and multi-tenant access control
The most expensive RLS mistakes in Supabase and PostgreSQL: incomplete policies, overpowered roles, fragile JWT assumptions, exposed service_role keys, and false confidence.
Supabase and HR software: configuration mistakes that expose payslips
The Supabase mistakes that matter in HR or payroll software: incomplete RLS policies, overly open buckets, and weak organization boundaries.
Sources
Related services
If this topic maps to a real risk in your stack, these are the most relevant CleanIssue audits.