Multi-tenant RAG: isolating each client's data
23 mai 20262 min read
In short
- As soon as an assistant serves several clients, none must see another's data, neither via search nor via the AI.
- The right approach: push the isolation into the database (PostgreSQL/Supabase Row Level Security), not only into the application code.
- On the RAG side: filter the vector search by tenant (metadata) and replicate the access rights of the source information system.
- The pitfalls that cause leaks: the
service_rolekey that ignores everything, joins to an unprotected table, and claims that the user can modify.
The problem, concretely
BeForBuild.com is multi-tenant: each client has their files, their contracts, their documents. The absolute nightmare would be a query (or an AI agent answer) leaking one client's data to another. In B2B, that is the mistake that kills trust, and the contract.
Why doing it in the code is not enough
Filtering "WHERE tenant_id = ?" in every application query works… until a developer forgets, a new endpoint, a poorly thought-out join. The isolation must be guaranteed at the database level, where no query can escape it.
How we isolate, for real
1. Row Level Security (RLS) in PostgreSQL
With Supabase/PostgreSQL, you define SQL policies that the database evaluates on every query: USING for reads, WITH CHECK for writes. Each row is attached to a tenant; it is impossible to step outside it, even through an application error. Best practice: treat these policies as code (versioned, tested, reviewed).
2. Filtering the vector search
The RAG search is bounded to the tenant via the metadata (each vector carries its tenant_id). With pgvector, the advantage is clear: RLS also applies to the vector table, data and embeddings in the same place, same rules.
3. Replicating the information system's rights
If a document is restricted to certain users in your document management, the assistant must respect the same restriction. RAG must never become a way to bypass existing permissions.
The pitfalls that cause leaks (the expert's nuance)
- Supabase's
service_rolekey bypasses RLS: to be used only server-side for admin jobs, never with a user query. - Joins: a policy on table A does not protect joined table B; you need RLS on every table touched.
- Enabling RLS without a policy locks everyone out: you create the policies immediately.
- Basing a policy on
user_metadata(modifiable by the user) is a flaw: you rely on trustworthy claims (app_metadata, an attachment table). - Index the columns used in the policies, otherwise performance collapses at scale.
In practice
It is structural on BeForBuild.com: several AI agents run on a multi-tenant database secured by RLS, each client perfectly isolated, vector search filtered by tenant. It is this proven base that I adapt to your context, with versioned and tested policies.
Sources
Frequently asked questions
Have a feature in mind? Let's talk.
30 minutes to scope your need and quote the fixed price. Reply within 24h.
Book my free audit · 30 min