Step 1: Stand up DV-RAG and trace one benign request
You are the defender on DV-RAG-Support, ACME Cloud's multi-tenant customer-support assistant. It is a real Retrieval-Augmented Generation (RAG) service, not a chatbot you talk to directly. A customer asks a question and the service runs four stages:
embed turn the question into a vector (NVIDIA llama-nemotron-embed-vl-1b-v2 embeddings)
retrieve find the most similar documents in the Milvus vector store (top-k),
filtered to the caller's tenant
generate paste those documents into the prompt and ask the LLM to answer
render the chat client renders the answer's markdown
Before you defend anything, stand the service up and trace one benign request so
you know what "normal" looks like. Every later step measures a deviation from this
baseline. Two facts matter here. The knowledge base is multi-tenant: each
kb_*.md carries a tenant: in its frontmatter, and retrieve() is meant to
scope results to the caller plus shared public docs. And there is no secret in the
system prompt; the sensitive data lives in the retrieved records, exactly the
shape of a real RAG leak.
1. Build the vector index, then ask a benign in-tenant account question.
Hit Run, or in the terminal:
python3 dvrag.py --build
python3 dvrag.py "What is on my account record?"
--build reads every kb_*.md, runs each through the ingestion firewall
(firewall.scan_document, accept-all as shipped), chunks it, embeds it, and writes
the Milvus index to /home/labuser/kb.db. The second call runs the full
embed -> retrieve -> generate -> render flow once as the authenticated Globex
caller.
2. Read the RETRIEVED: line and confirm the caller gets their OWN record.
You are the Globex account holder. The retriever should pull kb_account.md, the
Globex account record (account reference GLOBEX-ACR-88231, billing contact,
plan), and nothing from another tenant. That confirms in-tenant retrieval works
cleanly before you start attacking it.
3. Read retrieve(), build_index(), and scan_document() in the source.
build_index()offers every document tofirewall.scan_document()first; the shipped firewall (firewall.py) returnsTruefor everything.retrieve()builds its tenant filter from atenant_scopeargument. Note where that scope comes from; you will harden it in Step 5.
Pass criteria
The vector index is built (/home/labuser/kb.db exists) and a benign account
question retrieves the caller's own Globex record (kb_account.md) and no
other tenant's row. That confirms the stack is live and in-tenant retrieval is
clean.
firewall.pykb_account-recovery.mdkb_account.mdkb_billing.mdkb_getting-started.mdkb_globex-runbook.mdkb_initech-contract.mdkb_security-sso.mdkb_shipping.mdkb_status-incidents.mdlistener.py