Blog Verification

The Tenant Bleed: Why Your RAG App Needs Row Level Security

February 14, 2026 • PrevHQ Team

We’ve all built the demo.

You ingest a PDF. You vector search it. It works perfectly. Then, you add a second user.

Suddenly, your simple vector_store.similarity_search("Q3 Strategy") becomes a liability. If you forget to add filter={tenant_id: "acme"} in just one place—one specific line of code in one specific API route—User B sees User A’s confidential strategy.

This is the Tenant Bleed. In 2026, it is the number one cause of data breaches in B2B AI applications.

The Diagnosis: Metadata Filtering is Fragile

Most vector databases (Pinecone, Weaviate, Qdrant) encourage “Metadata Filtering” for multi-tenancy. You tag every chunk with a tenant_id. You filter by that tag at query time.

This works… until it doesn’t.

  • Developer Error: It relies on the application code to remember the filter. One junior developer, one hurried PR, one refactor, and the filter is gone.
  • Default Behavior: If the filter fails or is omitted, most databases return everything. The default state is insecure.
  • Performance: As your index grows to 100 million vectors across 10,000 tenants, filtering after the vector search (or even during) can lead to slow queries and high latency.

Security should not be optional. It should be enforced by the database.

The Pivot: Move Security to the Database

The solution isn’t “better code reviews.” It’s Row Level Security (RLS). Instead of asking the application to filter data, we ask the database to hide data that the user isn’t allowed to see.

This is why Supabase (Postgres + pgvector) has become the default stack for B2B RAG in 2026.

With Postgres RLS, you define a policy once:

create policy "User can only see their own vectors"
on embedding_table
for select
using (auth.uid() = user_id);

Now, when your application runs select * from embedding_table, Postgres automatically appends where user_id = 'current_user'. Even if your developer tries to query all data, the database returns zero rows for other tenants. The default state is secure.

The Solution: Enforced Isolation with Supabase

Here is how you architect a leak-proof Multi-Tenant RAG system:

  1. Identity Awareness: Your application authenticates the user (JWT).
  2. Context Propagation: The database session is initialized with the user’s ID.
  3. RLS Enforcement: The database policy silently filters every query—vector search, keyword search, or raw SQL.

You can sleep at night knowing that even a “select * from all_secrets” will return an empty list for a malicious tenant.

Verify the Invisible

How do you prove it works? “It works on my machine” isn’t enough for a SOC2 auditor.

This is why we use PrevHQ. We spin up ephemeral, isolated environments for every PR. In these environments, we launch a “Red Team” agent. This agent attempts to act as Tenant B and query Tenant A’s documents.

If the agent retrieves any document from Tenant A, the PR is blocked. We don’t just trust the RLS policy; we test it against a simulated adversary.

FAQ

Q: Is Supabase RLS slower than metadata filtering? A: In 2026, Postgres RLS is highly optimized. While there is a small overhead for policy checks, it is often faster than metadata filtering on large datasets because the query planner can optimize the scan using the tenant ID index before performing the expensive vector similarity search.

Q: Can I use Pinecone for multi-tenancy? A: Yes, Pinecone offers “Namespaces” which are a strong form of isolation. However, if you need complex permission rules (e.g., “User X can see Doc Y but not Doc Z within Tenant A”), RLS on Postgres offers far more granular control than simple namespacing.

Q: How do I migrate my existing RAG app to Supabase? A: You can use pgvector to store embeddings directly in your existing Postgres database. Tools like LangChain and LlamaIndex have first-class support for Supabase Vector Store, making the switch relatively painless.

Q: Does RLS work with vector search? A: Yes. pgvector indexes (HNSW/IVFFlat) work seamlessly with RLS. The database filters the visible rows first, then performs the nearest neighbor search on the remaining subset.

← Back to Blog