Exploit a Vulnerable RAG Pipeline
Break multi-tenant isolation in a deliberately vulnerable RAG pipeline — make retrieval leak another tenant's document into your answer, in a disposable local sandbox with synthetic data only.
PremiumProblem
Retrieval-Augmented Generation (RAG) systems answer questions by first fetching relevant document chunks from a vector store, then handing those chunks to an LLM as context. In a multi-tenant product — think a SaaS where Tenant A and Tenant B each upload their own private documents — the retrieval step *must* filter by tenant before anything reaches the model. If it doesn't, the model can be handed Tenant B's confidential text while answering a question asked on behalf of Tenant A, and it will happily quote from it, because from the model's point of view every retrieved chunk is just "context it was given to use." This is a cross-tenant data leak hiding inside what looks, on the surface, like a working feature: the chatbot answers questions correctly most of the time, right up until retrieval pulls in the wrong tenant's chunk and the model repeats whatever is in it — including, in this lab, a document that was deliberately "poisoned" with instructions to leak a secret flag. In this lab you'll run the **DARE Vulnerable AI Suite** locally and attack its `vulnerable-rag` challenge: send ordinary questions as `tenant-a` until retrieval accidentally surfaces a chunk that belongs to `tenant-b`. > Practice only against the disposable `dare-vulnerable-ai` suite running > locally on `127.0.0.1:8000` (bound to localhost only), with synthetic > seed documents. Never attempt cross-tenant retrieval attacks against a > real multi-tenant product or any system you do not own or have explicit > written authorization to test.
Objectives
By the end of this lab you will be able to:
- Explain why tenant filtering in a RAG pipeline must happen at the
vector store / retrieval layer, not only in the API response. - Enumerate a RAG challenge's document inventory without reading full
contents, to understand what's in scope. - Probe a RAG query endpoint with varied questions to surface a
cross-tenant retrieval leak. - Recognize a "poisoned document" planted to test for exactly this
class of leak. - Read a challenge's official solution and articulate why an
API-layer-only fix would be insufficient.
Prerequisites
To complete this lab you'll need:
- Docker and Docker Compose installed.
- Access to the private
dare-vulnerable-airepository (enrolled
students have access):git@github.com:darelabs-tech/dare-vulnerable-ai.git. -
curl(or an equivalent HTTP client) and a terminal. - Basic familiarity with JSON; no prior RAG/vector-database experience
required.
The mental model: retrieval is a search query, not a fact
A RAG system's retrieval step is, underneath the marketing term, a
similarity search: "find the N chunks whose embeddings are closest to
this question's embedding." Similarity search has no innate concept of
"tenant" — it just ranks by vector distance. If a poisoned or merely
topically-similar document from another tenant happens to score high
enough, it gets pulled in, full stop, unless something upstream of
ranking excluded it first.
Vulnerable retrieval flow
question ──▶ embed ──▶ similarity search across ALL tenants' vectors
│
▼
top-N chunks (tenant filter applied where? nowhere)
│
▼
handed to the LLM as context
Why this is worse than it looks
A tenant-filtering bug in a REST endpoint is usually visible in the API
response shape — you'd notice tenant_id: "tenant-b" showing up
somewhere it shouldn't. In RAG, the leak is laundered through natural
language: the model paraphrases or quotes the leaked chunk as part of a
fluent answer, so it can slip past reviewers who are only checking "does
the answer sound reasonable," not "did this answer just repeat another
customer's private document."
How you'll work through this lab
- Unlock the
vulnerable-ragchallenge. - List its documents to understand what's in the corpus (without
reading full content). - Send varied questions as
tenant-auntil atenant-bchunk shows up
inretrieved_chunks. - Confirm
solved: trueand inspect the leaked snippet. - Read the official solution and explain why the fix belongs in the
vector store, not the API layer.