Back to blog
Supabasetechnicalguide

Supabase RLS: the 5 mistakes we find in every HR SaaS audit

Published on 2026-06-069 min readCleanIssue

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.

Sources

Written by CleanIssue
Reviewed on 2026-06-06

Related services

If this topic maps to a real risk in your stack, these are the most relevant CleanIssue audits.

Need an external review of your HR SaaS?

Share your product, stack, and client context. We will come back with the right review scope.

Discuss your audit