Home/Case Study/Multi-Tenant PostgreSQL RLS Leak
SECURITY & ROW-LEVEL AUDIT // CASE STUDY #06

Plugging the Multi-Tenant Leak: Enforcing PostgreSQL RLS to Prevent Cross-Account Data Access

How a missing tenant_id filter in a Supabase/PostgreSQL SaaS application allowed Company A users to manipulate API URLs (IDOR Vulnerability) to inspect Company B’s confidential invoices, and how we enforced database-level Row Level Security (RLS) in 48 hours.

DATA LEAK RISK0 VulnerabilitiesCross-Account Blocked
COMPLIANCE STATUSSOC2 & GDPR PassedTenant Isolation Verified
SECURITY AUDIT SPEED48 HoursPolicy & Test Suite Deployed
HawkInspect Pro Security Audit Team
10 min readVerified Case Study

1. Executive Summary & Vulnerability Discovery

A B2B SaaS company offering invoicing and financial workflow software built on Supabase & PostgreSQL contacted HawkInspect Pro during a pre-funding enterprise CTO security evaluation.

During automated penetration testing, an enterprise client discovered an Insecure Direct Object Reference (IDOR) flaw: authenticated users from Company A could change the invoice ID in the API URL (e.g. from /api/invoices/inv_1001 to /api/invoices/inv_1002) and inspect private billing records, customer names, and bank details belonging to Company B.

Multi-Tenant Database Reality:Filtering by tenant_id in JavaScript/TypeScript API routes works until a developer forgets the WHERE clause in a single endpoint. Database-level PostgreSQL Row-Level Security (RLS) guarantees complete tenant isolation regardless of code bugs.

Concerned about multi-tenant data leaks in your SaaS?

We perform line-by-line PostgreSQL RLS policy audits and API middleware tenant isolation testing in 48 hours.

2. Quantifying the Cross-Account Data Leak Risk

Impact of the IDOR Multi-Tenant Leak

  • Cross-Tenant Data Exposure: 100% of tenant tables lacked database-level Row Level Security (RLS) policies.
  • SOC2 & GDPR Compliance Failure: Unisolated data access violated SOC2 Type II Trust Principles and GDPR data confidentiality requirements, threatening enterprise customer contracts.
  • Systemic Human Error Risk: Relying on application-level WHERE tenant_id = req.user.tenantId logic meant any new developer could accidentally introduce a data leak.

3. Vulnerable API Endpoint & Missing RLS

The original Node.js / Supabase route queried the database directly by invoice ID without enforcing or verifying tenant ownership at either the route or database engine level:

VULNERABLE ROUTE // MISSING TENANT ISOLATION
app/api/invoices/[id]/route.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { NextResponse } from 'next/server';
import { supabase } from '@/lib/supabaseClient';
export async function GET(req: Request, { params }) {
const invoiceId = params.id;
// 🚨 VULNERABILITY: Missing tenant_id verification and database RLS!
// Any authenticated user can read invoiceId belonging to Company B!
const { data, error } = await supabase
.from('invoices')
.select('*')
.eq('id', invoiceId) // ❌ Missing .eq('tenant_id', currentTenantId)
.single();
if (error) {
return NextResponse.json({ error: error.message }, { status: 404 });
}
return NextResponse.json({ invoice: data });
}

4. Anatomy of the IDOR Cross-Tenant Exploitation

An attacker authenticated as User X from Tenant A sends a legitimate HTTP GET request. By substituting the UUID parameter in the URL with an invoice ID belonging to Tenant B, the Supabase client returns Tenant B's raw database record because the table lacked PostgreSQL RLS policies and the endpoint lacked tenant verification.

5. Enforcing PostgreSQL Row Level Security (RLS)

HawkInspect Pro deployed a mandatory PostgreSQL Migration script enforcing RLS policies across all tenant tables. PostgreSQL now extracts tenant_id directly from the cryptographically signed JWT token:

HARDENED // POSTGRESQL RLS ENFORCED
supabase/migrations/20260911_enforce_tenant_rls.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 1. Enable Row Level Security on the Invoices Table
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;
-- 2. Create Tenant Isolation Policy for SELECT Operations
CREATE POLICY tenant_isolation_select_policy
ON invoices
FOR SELECT
USING (
tenant_id = (auth.jwt() ->> 'tenant_id')::uuid
);
-- 3. Create Tenant Isolation Policy for INSERT / UPDATE Operations
CREATE POLICY tenant_isolation_modification_policy
ON invoices
FOR ALL
WITH CHECK (
tenant_id = (auth.jwt() ->> 'tenant_id')::uuid
);

6. Automated API Middleware Tenant Isolation Testing

To guarantee zero regressions in future deployments, we built an automated API Middleware integration test suite. The test runner spawns mock users from Tenant A and Tenant B, executing cross-tenant requests across all API routes to verify HTTP 403 Forbidden responses.

7. SOC2 & GDPR Compliance Impact

Zero Vulnerabilities

All multi-tenant API routes verified with 0 cross-tenant data leaks.

SOC2 & GDPR Audit Passed

Passed formal enterprise security due diligence and data isolation controls.

48-Hour Turnaround

Complete database migration, RLS policies, and test suite shipped in 48h.

HAWKINSPECT PRO EMERGENCY SHIELD

48-Hour Multi-Tenant Security & PostgreSQL RLS Audit

Protect your SaaS from cross-tenant data leaks, IDOR flaws, and compliance failures. Our principal security engineers audit your database policies and API routes in 48 hours.

9. Interactive Multi-Tenant Security Diagnostic

MULTI-TENANT SECURITY CHECKLIST WE AUDIT

SCORE:0 / 5
Q1:Are PostgreSQL Row Level Security (RLS) policies enabled on all multi-tenant tables?
Q2:Does your database automatically reject queries when tenant_id context is missing?
Q3:Do you have automated integration tests simulating cross-tenant API data requests?
Q4:Are customer invoices and billing payloads isolated by verified JWT tenant claims?
Q5:Have you passed SOC2 / GDPR cross-tenant data isolation compliance checks?
ASSESSMENT RESULT:0 / 5 PASSED

CRITICAL MULTI-TENANT LEAK RISK

High vulnerability to IDOR cross-tenant data access. Urgent PostgreSQL RLS policy enforcement recommended.

FORWARD TO YOUR ENGINEERING TEAM1-CLICK SHARE

Non-coder founder? Forward this article link directly to your CTO, Tech Lead, or Dev Agency to verify if your PostgreSQL tables have Row-Level Security (RLS) policies enabled:

11. Multi-Tenant Security FAQ

An IDOR occurs when an application exposes database records via URL parameters or API inputs (e.g. /api/invoices/inv_9921) without verifying if the requesting user belongs to the target tenant organization. Attackers simply change the ID in the API request to view other companies’ confidential data.
Relying solely on developers to remember WHERE tenant_id = x in every single API endpoint leads to human error. A single forgotten WHERE clause in an obscure endpoint leaks private data across tenants. PostgreSQL Row Level Security (RLS) guarantees tenant isolation at the database engine layer, enforcing isolation automatically even if application code omits the filter.
Supabase injects the authenticated user’s JWT token into PostgreSQL session context (auth.jwt()). By defining an RLS policy like (tenant_id = (auth.jwt() ->> 'tenant_id')::uuid), PostgreSQL automatically filters every query, insert, and update to match the user's tenant ID with zero performance penalty.
Our principal security engineers complete a comprehensive multi-tenant isolation audit, RLS policy deployment, and automated API middleware isolation test suite within 24 to 48 hours.
12 // FINAL POSTMORTEM

12. What Was Actually Wrong & The Question Worth Asking

Not PostgreSQL engine speed. Not Supabase API limits. Not Next.js router performance.

The vulnerability was a Missing PostgreSQL Row Level Security (RLS) Policy that allowed cross-account data access (IDOR) via simple URL parameter edits.

What Un-Audited SaaS Code Does:

WHERE id = req.params.id (No RLS)

Company A user can edit the ID in URL and inspect Company B private invoices.

What HawkInspect Pro Enforces:

CREATE POLICY ... USING (tenant_id = auth.jwt()->>'tenant_id')

Database engine enforces tenant isolation automatically. 0 Leaks.

01

Database-Level RLS Policy Enforcement

Never rely solely on application-level JavaScript WHERE tenant_id = x filters. Enforce PostgreSQL RLS policies at the database engine layer.
02

JWT Tenant Claim Verification

Extract tenant claims directly from cryptographically signed JWT tokens in Supabase/PostgreSQL sessions.
03

Automated Isolation Test Suite

Run automated API middleware integration tests that simulate cross-tenant data requests on every CI/CD deployment.
THE QUESTION WORTH ASKING FOR YOUR MULTI-TENANT SAAS:

If an authenticated user changes tenant_id or resource ID in an API URL today, does PostgreSQL block the request with RLS or leak private customer data?

If the answer is “We don't have RLS policies enabled,” you don't just have a minor bug — you are exposed to cross-tenant data leaks and SOC2 compliance failures.
IS YOUR MULTI-TENANT SAAS VULNERABLE TO CROSS-ACCOUNT DATA LEAKS?

Get Your Multi-Tenant Database Security Audited

Talk directly with our Principal Security Auditor. We'll inspect your PostgreSQL RLS policies, Supabase claims, and API endpoints in 48 hours.