Back to Technical Mode
RAG / Vector Store Cross-Tenant Data Leakage
Retrieval-Augmented Generation pulls relevant documents from a vector database to give the model context. If that similarity search isn't scoped to the requesting user's own tenant/organization, a query can retrieve — and the model can then summarize or quote from — documents that belong to a completely different customer.
A vector similarity search finds the closest-matching embeddings across whatever it's given to search — it has no inherent concept of 'ownership' unless the query explicitly filters for it
This is architecturally the same bug as a SQL query missing a WHERE tenant_id = ? clause — it's IDOR, just at the embeddings-index layer instead of the row layer
Embedding the tenant/user ID as metadata on every vector at ingestion time, and filtering the search by it, is the standard and necessary fix — most vector databases support metadata filtering natively
Testing this requires an actual cross-tenant query, not just checking that same-tenant retrieval works — a passing 'happy path' test suite can completely miss this class of bug
Want this flow explained in detail as it plays?
POST/api/ask
Headers
Host:app.flowground.dev
Content-Type:application/json
Body
Loading...
Quick-Insert Payloads
Defense in depth
- Tag every vector at ingestion time with the owning tenant/user ID as metadata, and require that filter on every single retrieval query — never run an unscoped similarity search against multi-tenant data
- Enforce the tenant filter at the database/query layer, not just in application code that calls the vector store — a missed filter in one code path shouldn't be able to bypass it
- Write an explicit cross-tenant retrieval test (as tenant A, query for content only tenant B has) as part of the standard test suite for any RAG feature, not just single-tenant happy-path tests
Vulnerable vs. Fixed
async function retrieveContext(question) {
const embedding = await embed(question);
return vectorDb.query({ vector: embedding, topK: 5 }); // searches the entire index, every tenant included
}