
RAG security testing attacks the retrieval pipeline, not only the model: poisoning, indirect injection in chunks, ACL and tenant leakage, filter injection, embedding inversion, and output exfiltration. The breaches that matter happen earlier, in connectors, indexes, and filters. Prove each class in a staging index with canary documents, a second tenant, and traces that name the document IDs that reached the context window.
The short answer
- RAG security testing proves which document IDs enter the context window, using canaries, a second tenant, and retrieval traces.
- The seven classes are document poisoning, indirect injection in chunks, ACL bypass after chunking, tenant leakage, metadata filter injection, embedding inversion, and output or cost abuse.
- Fixes that stick are server-side ACL predicates, real tenant isolation, ingestion sanitization, and inert rendering, reported with answer-quality delta.

Key takeaways
- RAG security testing scores whether the wrong document IDs enter the context window, not whether the model will say a banned phrase.
- Trust changes at four boundaries: ingestion, index isolation, query construction, and output rendering.
- PoisonedRAG-style attacks are counted in documents per question, not as a percentage of the corpus. Re-measure on your embedder and top-k.
- A tenant or ACL predicate must be built from the authenticated session in server code and applied inside the search, not after ANN top-k.
- EchoLeak (CVE-2025-32711) showed a retrieved chunk plus a rendered output channel is enough for exfiltration. Inert rendering and an egress allowlist close that path.
- Ship attack success, clean-corpus utility, latency, cost, and purge time in one report. A control that wrecks answers will be rolled back the week after the pen test report lands.
What is RAG security testing, and how is it different from LLM red teaming?
RAG security testing is an assessment of the retrieval path that decides which text the model is allowed to see. You score it by whether the wrong document IDs enter the context window.
LLM red teaming mostly probes the model: jailbreaks, policy evasion, and what the assistant says if you ask directly. A retrieval test asks a different question. Given this user, this tenant, and this query, which chunks came back, at what scores, and did any of them change the answer or the tool call?
The 2025 OWASP Top 10 for LLM Applications already splits the work. LLM01 covers prompt injection. LLM02 covers sensitive information disclosure. LLM04 covers data and model poisoning. LLM05 covers improper output handling. LLM08 covers vector and embedding weaknesses: cross-tenant leakage, inversion, and poisoning. Confirm the published numbering before you cite it in a customer report.
A chat-only engagement can light up LLM01 and still miss LLM08. The vulnerable code is often the connector, the chunker, the filter builder, or the markdown renderer. Related entries that show up on the same path are LLM06 excessive agency, LLM07 system prompt leakage, and LLM10 unbounded consumption.
Quality evals do not substitute. RAGAS and TruLens measure faithfulness and context precision. They will not tell you that tenant B retrieved tenant A’s chunk, or that a canary in a PDF annotation was followed as an instruction. Run both. Report attack success and utility side by side.
Where does trust change in a RAG pipeline?
Trust changes at four boundaries: ingestion, index isolation, query construction, and output rendering. Everything else is plumbing between those four decisions.
Map the real path before you send a single query. Connectors pull SharePoint, Confluence, Drive, tickets, mail, and wikis. A parser and chunker turn files into passages. An embedder writes vectors. The store serves approximate nearest neighbor search, often hybrid with BM25, sometimes followed by a cross-encoder reranker or an LLM query rewriter.
Common stores in production stacks are Pinecone, Weaviate, Qdrant, Milvus, Chroma, pgvector, Azure AI Search, Elasticsearch or OpenSearch, and MongoDB Atlas Vector Search. A context builder packs chunks. The model answers. A renderer and, in agent setups, tools act on that answer.
Most programs instrument only the last two hops: context into the model, and model output into the UI. The failures we keep seeing sit earlier. A shared indexer service account drops per-user identity. A metadata filter is concatenated from the client. A post-filter runs after ANN top-k, so out-of-scope neighbors affect scores and timing.
An LLM rewriter is injectable before retrieval starts. Log, on every test query, document ID, chunk ID, similarity, reranker score, the final context, and the rendered output. Without document IDs you cannot prove a poisoning or ACL finding, and you cannot disprove one either.
Parser bugs are in scope too. Office XML external entities and decompression bombs are ingestion-path exposure and denial of service with no model involved.
How do document poisoning and indirect prompt injection reach the context window?
Document poisoning places a few attacker-controlled passages where your connector will index them, so retrieval prefers those passages for a chosen question. Scale is counted in documents, not in percent of the corpus.
The PoisonedRAG paper (Zou et al., USENIX Security 2025) is the reference result on public QA sets: Natural Questions, HotpotQA, and MS MARCO. Published write-ups describe high attack success from a handful of injected passages per target question, often discussed as about five passages and success near 90 percent. Treat those figures as paper-specific. Re-measure on your corpus, your embedder, and your top-k.
The operational point holds regardless of the paper’s exact numbers. You do not need to own the library. You need a handful of chunks that outrank the real answer for one question. AgentPoison (2024) showed the same shape against agent memory and RAG knowledge bases: a very small poisoned fraction, high success on the attacker’s questions, and little change in benign retrieval. Do not quote an attack-success rate you have not reproduced.
Carrier surfaces are whatever the connector treats as text. HTML comments and visually hidden copy, including display:none and white-on-white, PDF metadata and annotations, image alt text, Office comments and tracked changes, YAML front matter, filenames, sitemap or robots text, and any wiki page or support ticket a user can edit. “Internal only” is not a boundary if vendors, customers, or staff can write into a source you ingest.
The staging test is a canary, not a production poison. Insert a small set of marked passages that answer one synthetic question only if retrieved. Query that question. Record whether the canary IDs land in top-k and whether the answer repeats the canary. Then measure purge time: delete or revoke the source object and clock how long the chunk remains retrievable. That lag is the incident metric.
Do not “fix” poisoning by cutting top-k until the product stops answering. Pair attack success rate with a clean-corpus utility check, faithfulness or task accuracy, before and after the control.
Indirect prompt injection is the same carrier with a different payload. Greshake et al. (2023) showed that any content the model reads is an instruction channel: web pages, PDFs, emails, tickets, code comments. RAG makes the channel reliable, because retrieval actively pulls the attacker’s text into the window.
Plant a canary instruction in a document the retriever will select. Ask a benign question that should retrieve it. Pass criteria are behavioral and forensic. The retrieval trace contains the canary chunk ID. The answer follows the canary: a marker phrase, a tool attempt, or a link the user did not request. A second run with the canary absent does not.
Two adjacent components deserve their own cases. A cross-encoder reranker can be biased by keyword-stuffed chunks that win the rerank even when dense similarity is mediocre. An LLM query rewriter sits in front of the index and can be steered by the user, or by content already in memory, before any document ACL is applied.
Microsoft’s spotlighting work (delimiting, datamarking, or encoding untrusted spans) measurably lowers success on some injections. It is a mitigation. It does not authorize the chunk. Pair it with prompt injection testing that covers direct user input and retrieved content as separate cases.
How do ACLs, tenant isolation, and metadata filters fail?
Document-level ACLs fail when the predicate that was true for the file is not the predicate applied to the chunk at query time. The index then serves text the user cannot open in SharePoint, Confluence, or Drive.
The usual breaks are mechanical:
- The ACL is enforced in the UI of the source system and never sent to the retriever.
- Permissions live on the parent document and are dropped when the file is split into chunks.
- The indexer runs as one shared service account, so the index no longer knows the reader.
- Revocation is eventual: a group change today is still retrievable until the next reindex, sometimes for hours or days.
- Nested AD or Entra groups, or inherited SharePoint permissions, are expanded incompletely.
The predicate has to be built in trusted application code from the authenticated identity and attached to the query. The model must not invent it. The client must not supply it. When the filter key is attacker-influenced, that is CWE-639, authorization bypass through a user-controlled key.
Test with two users and one parent document that only user A may read. Chunk it. Query as user B with terms that uniquely hit those chunks. Fail if any chunk ID from that parent appears, including via hybrid BM25. Repeat after you remove A from the group, and record the lag until the chunk disappears.
Repeat the test across an embedding-model upgrade, when old vectors and new ACLs coexist. Index-time ACL copies go stale. Query-time predicates stay fresh and cost a filter. Answer-time redaction is the weakest: the secret has already entered the prompt, the logs, and often the trace store.
Multi-tenant vector stores leak when isolation is a filter the application must remember, and one query path forgets it. The marketing name is not the measure of strength. What matters is the blast radius when the control is missing.
| Isolation | Blast radius if the app errs | Operational cost |
|---|---|---|
| Physical cluster or index per tenant | One tenant | Highest |
| Collection per tenant | Usually one tenant | High |
| Namespace or partition (Pinecone namespaces, Milvus partition keys) | One namespace, if the key is server-side | Moderate |
| Metadata filter on a shared index | Every tenant in the index | Lowest, and weakest |
Post-filtering makes this worse. Approximate nearest neighbor search (HNSW, IVF) returns top-k first, then the ACL or tenant predicate throws rows away. Recall drops, because the neighbors you were allowed to see never made the candidate list. Where scores or latency depend on how many hits were discarded, the same pattern can reveal that out-of-scope documents exist.
Single-stage filtered search keeps the predicate inside the scan. pgvector 0.8.0 added iterative index scans to recover recall when a filter rejects most ANN candidates. That is a retrieval-quality change, not an authorization control. Qdrant documents query planning from payload indexes. Weaviate documents filtered vector search. Confirm the mode, index type, and version you actually run. Defaults drift. Do not copy a vendor algorithm name into a finding until the docs for that version say it.
The test is a second tenant. Load a unique canary into tenant A. Query as tenant B with the canary’s rare terms, through every API route: chat, retrieval API, admin debug, reranker preview, and any MCP tool that wraps search. Fail on any trace that contains A’s document ID, or on timing and score deltas that exist only when A’s vector is present. A forgotten filter on one code path is a full leak of the shared index. The same session-built predicate from the ACL test is what must be applied here. Do not rebuild it per route.
Metadata filter injection is the structured cousin of that miss. Vector engines accept real query languages: Pinecone takes JSON filter objects, Chroma takes a where dictionary, MongoDB Atlas Vector Search takes a filter document, Weaviate exposes GraphQL where clauses, and Elasticsearch and OpenSearch take query DSL. The bug is in the application. It shows up when filters are built by string concatenation, template interpolation, or by forwarding client JSON into the driver.
Impact matches CWE-943 when an attacker-controlled predicate, often an always-true clause, returns chunks the caller should never see. Send a query whose user-controlled portion tries to widen a field the client may not set: tenant, ACL group, or classification. A safe builder ignores unknown keys, rejects unexpected operators, and overwrites tenant and ACL from the session after parsing.
A vulnerable builder returns canary IDs from another tenant or another clearance. Parameterize. Allowlist field names and operators in server code. Never let the model assemble the filter, and never merge a client filter over the authorization predicate. LangChain, LlamaIndex, and hand-rolled retrievers grow this bug the moment a filters argument is passed through from the request body.
Can embeddings be reversed, and how does rendering become exfiltration?
Yes. Embeddings of short text can be reconstructed closely enough that the vector store is sensitive data. Under GDPR and CCPA/CPRA it is personal data when the corpus is.
The vec2text line of work (Morris et al., 2023, “Text Embeddings Reveal (Almost) As Much As Text”) reported high-fidelity recovery of short passages from dense embeddings, including exact recovery for a large share of inputs of roughly 32 tokens. Longer chunks degrade. Names, ticket IDs, and secret-shaped strings are often short. Re-test against the embedding model you ship. Fidelity is model-specific.
Embeddings are not anonymization. Anyone who can read the index, a snapshot, or an export can attempt inversion offline. Membership inference needs even less. With query access, similarity against a candidate passage indicates whether that document is indexed. That matters for M&A, legal hold, and HR corpora, where “is this memo in the bot” is itself a secret.
Controls are the same ones you would put on the source files. Encrypt the index at rest, restrict export and backup roles, keep vectors inside the tenant boundary described above, and minimize what you embed. A reindex does not wash this away. Old snapshots remain invertible until they are destroyed. GDPR Article 32 security duties and access requests both reach this store if a person can be singled out from a chunk.
Rendering becomes exfiltration when the app treats model output as active content, and a retrieved chunk causes the assistant to emit a URL the browser or a tool will fetch. It becomes a bill when top-k, chunk size, and retries are unbounded.
If the chat UI renders markdown, a hostile chunk can push the model to emit an image or link whose query string carries other retrieved text. The user’s browser then requests an attacker-controlled host. Researchers described EchoLeak (CVE-2025-32711) in Microsoft 365 Copilot as a zero-click case of this chain: indirect injection arrived through content the product retrieved, and the output channel completed the leak. Microsoft patched the issue. Confirm patch status on the tenant you test.
The same pattern exists wherever agents have tools. A retrieved instruction that calls an HTTP tool, a mail tool, or an MCP client is an egress path the security team never audited. Map a successful fetch to LLM05 and to CWE-200. Fix the renderer: no remote images, an allowlist of URL schemes, escaped markup by default, and a network egress policy on tool calls. Detectors that scan the answer are a backstop, not the boundary.
Denial of wallet is the quieter sibling, and it lines up with LLM10 unbounded consumption. A query that forces a huge top-k, oversized chunks, a rewriter retry loop, and a long context multiplies embedding, rerank, and completion cost. Put a hard context budget and a max top-k in the retriever, not in a prompt. Alert on per-tenant token and latency outliers. One crafted query should not be able to move the monthly bill.
Which fixes, CI checks, and framework evidence actually hold?
The fixes that hold are architectural: server-side ACL predicates, physical or collection-level tenant isolation, sanitized ingestion, and strict output rendering. Guardrails lower rates. They do not decide authorization. Those four controls map back to the four trust boundaries: ingest, isolate, query, render.
| Control | What it actually stops | What still fails |
|---|---|---|
| LLM firewall or output classifier | Some known injection phrasing | New phrasing, authorized-looking tool calls, retrieval of the wrong chunk |
| Spotlighting or datamarking | A share of indirect injection | ACL bypass, tenant filters, inversion of the index |
| Query-time ACL predicate, single-stage filtered ANN | Cross-user retrieval, post-filter recall loss | Wrong identity on the indexer, stale group expansion |
| Per-tenant index or collection | Forgotten metadata filter | Poisoning inside the tenant’s own corpus |
| Ingestion allowlist and hidden-text strip | Comments, annotations, tracking as carriers | Poisoning in visible text the business chose to trust |
| Egress allowlist and inert rendering | Markdown and tool exfiltration | The user copying a secret the model was allowed to say |
Source trust tiers belong at ingest. Vendor PDFs and anonymous tickets are a different tier from contracts your counsel published. Retrieval-time anomaly scores and post-hoc answer checks catch some poison late. They add false positives. Report detection lag next to attack success. Content provenance such as C2PA helps when publishers actually sign files. It does nothing for a wiki.
Every fix gets a utility delta: faithfulness or task accuracy, latency, and token cost, clean corpus versus controlled corpus. A control that drops accuracy by collapsing retrieval will be rolled back the week after the pen test report lands. Mean time to purge a poisoned document is the other product number. If reindex lag is six hours, say so in the report.
Auditors accept a repeatable method, per-class attack success, and artifacts that show which document IDs were retrieved. They do not accept a vendor questionnaire that says multi-tenancy is enabled. Map findings to the 2025 OWASP LLM entries above, and to MITRE ATLAS where the technique matches a published AML case.
NIST AI 100-2 is the adversarial machine learning taxonomy (evasion, poisoning, privacy, abuse) and sits next to the NIST AI RMF. ISO/IEC 42001 expects the AI management system to treat this pipeline as a system under control, with risk treatment you can show an auditor. ISO/IEC 27001 still covers the index as an asset: access, backup, logging. EU AI Act duties depend on whether the system is GPAI or high-risk. The engineering act is the same either way: keep the test record. GDPR Article 32 is implicated the moment embeddings or stale ACLs expose personal data.
A defensible report lists, per attack class, attack success rate, poisoned-document retrieval rate at your production top-k, clean-corpus utility before and after the fix, latency and cost delta, and purge time. Attach the traces. Name the engine version and whether filtered search was pre-filter, post-filter, or single-stage.
Harness it in CI against a staging index only. garak, PyRIT, promptfoo, DeepTeam, and Giskard can drive cases. None of them knows your ACL model until you script the canary, the second tenant, and the document-ID assertion. promptfoo is the easiest to wire to a pull request. PyRIT fits longer multi-turn agent traces. Schedule the suite on every connector change and every reindex.
MCP-exposed retrieval, agent memory, and migration jobs that dual-read old and new indexes are the same four boundaries with a new caller. A RAG pipeline security assessment should include those routes. Broader coverage of prompts, plugins, and output handling belongs in LLM application penetration testing. The seven classes above are the same failure modes we tracked in 10 real AI agent vulnerabilities of 2025.
What should the staging harness assert on every build?
Assert document IDs, not vibes. Each CI case loads a known canary, runs one query as a known principal, and fails if the trace contains a forbidden chunk ID or omits a required one.
Minimum suite, all on a staging index:
- Poison canary: one synthetic question, five or fewer marked passages, top-k and answer both checked, then a purge-time check after delete.
- Indirect injection canary: marker phrase and tool-call attempt, with a negative run after the chunk is removed.
- ACL pair: user B must not retrieve user A’s parent, including hybrid keyword search, plus a revocation-lag sample.
- Second tenant: rare-term query on every search route, including debug and MCP wrappers.
- Filter injection: client JSON that tries to overwrite tenant or clearance, expecting zero foreign IDs.
- Render: a chunk that asks for a remote image or off-allowlist tool call, expecting no outbound request.
Record engine version, filter mode, top-k, and embedder hash next to the pass/fail. A green run on last quarter’s index does not describe the cluster you shipped on Friday.


FAQ
What is RAG security testing?
It is an assessment of ingestion, the vector index, query filters, and output rendering. The pass/fail signal is which document IDs and scores reached the context window for a given user and tenant, not only what the model said.
How many documents does poisoning take?
PoisonedRAG and follow-on work show that a handful of passages aimed at one question can dominate retrieval on public QA sets. Count documents, not corpus percent, and re-measure on your embedder and top-k before you quote a success rate.
Is post-filtering a security bug?
It is both a recall problem and a leak risk. ANN top-k runs before the ACL or tenant predicate, so allowed neighbors may never be candidates, and score or timing differences can reveal out-of-scope vectors. Prefer a single-stage filtered scan with a server-built predicate.
Are vector embeddings personal data?
When the source text is personal data, the embedding is too. Short inputs have been inverted from dense vectors at high fidelity, and query-time similarity can support membership inference. Protect the index like the files it came from, including backups.
Which frameworks ask for this evidence?
OWASP LLM08, NIST AI 100-2, ISO/IEC 42001, ISO/IEC 27001 asset controls, and GDPR Article 32 all have a place for it. The EU AI Act's exact duties depend on GPAI or high-risk status. What they share is a demand for a repeatable test record, not a checkbox that multi-tenancy is on.
What belongs in CI versus a pen test?
CI should assert canary document IDs, tenant isolation, filter overwrite, and render egress on a staging index at every connector change. A pen test adds new carriers, identity edge cases, and a utility delta the product team can accept.
If your retriever is already in production, a RAG pipeline security assessment should show, with document IDs, whether a second tenant and a revoked user can still pull chunks. We run that against the index you actually ship, then retest after the predicate and isolation changes so answer quality stays in the report.