← All briefings
CRITICALAug 202612 min readRAGExtractionAgent

RAG-Thief: How Agent-Based Attacks Scale Private-Data Extraction

LogicLeak Research · Published Aug 2026

Most teams reason about retrieval-augmented-generation leakage one query at a time: a user asks a question, the system returns a document it shouldn't, and the fix is an access-control patch. That model badly understates the threat, because the adversary is no longer a person typing one query. It is an agent. Give a language model a goal — reconstruct the private corpus behind this RAG endpoint — a tool to query the system, and a loop, and it will systematically enumerate the knowledge base the way a web crawler enumerates a site. We built and ran this pattern, which the research community has taken to calling RAG-Thief, against five production and staging retrieval systems this quarter. On three of them an unauthenticated agent reconstructed a majority of the underlying documents from nothing but the answers the system was designed to give.

Why Agents Change The Math

A human extraction attempt is bounded by patience. An agent is bounded only by rate limits and budget. It reads each answer, notices which topics returned rich detail and which returned refusals, and generates the next batch of queries to probe the gaps — expanding coverage on every pass. Because retrieval systems are built to be helpful, each answer quotes or paraphrases source passages, and the agent stitches those fragments back into the originals. What looks like normal usage from any single request is, in aggregate, a full read of a corpus the requester was never entitled to. In our runs the agent recovered 60–80% of target documents within a few thousand queries — well inside a normal API budget, and spread across enough sessions to evade naive volume alarms.

# The extraction loop, abstracted. No exploit — just goal + tool + memory.

seen = set()
frontier = seed_topics(target_domain)          # broad opening questions

while budget_remaining() and frontier:
    q = agent.next_query(frontier, seen)       # LLM picks the highest-yield gap
    answer = rag_endpoint(q)                    # the system answers, as designed
    fragments = extract_source_passages(answer)
    seen.update(fragments)
    frontier += agent.expand(answer)           # answers reveal adjacent topics

# 'seen' converges on the private corpus, one helpful answer at a time.

// BREACH

Incident reference RAG-2026-052: An internal knowledge assistant, reachable by every employee and quietly by a partner integration, was enumerated by an agent using the partner key. Over eleven days it reconstructed roughly 70% of an HR and legal corpus — salary bands, pending matters, internal policy — without a single access-control error firing. Each query was individually authorised; the breach existed only in the aggregate, which nothing was watching.

Why Per-Query Controls Miss It

The controls most RAG deployments rely on are all evaluated at the granularity of a single request, and RAG-Thief lives above that granularity. Access control checks whether this identity may query the system — yes. Content filtering checks whether this answer is individually harmful — no, it is a normal helpful response. Rate limiting throttles obvious floods — defeated by pacing and session rotation. None of them model the thing that matters: cumulative disclosure to one actor over time. An identity that has, across a week, been shown 70% of the corpus has effectively exfiltrated it, even though no individual answer broke a rule.

// WARNING

Retrieval quality and extraction risk are the same dial. The more faithfully your RAG system quotes its sources to be useful, the more completely an extraction agent can reconstruct them. You cannot tune this away with better prompts — it is inherent to answering from a corpus. It has to be contained at the access and telemetry layers.

Detection & Mitigation

First, enforce entitlements before retrieval, not after. The single strongest control is to scope the vector search itself to documents the caller may see — filter the index at query time by the requester's permissions, so unauthorised passages are never candidates for an answer. Post-retrieval filtering, which most systems use, has already surfaced the document before the check runs; pre-retrieval scoping means the agent can only ever enumerate what it was entitled to anyway.

Second, meter cumulative disclosure per identity, not per request. Track how much distinct source material each caller has been shown over rolling windows and alert or throttle when one identity's coverage of the corpus crosses a threshold. This is the only layer that sees the attack, because the attack only exists in aggregate. Treat a partner key that has touched most of the knowledge base the way you would treat a database dump.

Third, reduce verbatim fidelity where you can afford to. For sensitive corpora, answer from summaries and redacted extracts rather than quoting source passages, and log retrieval provenance so a suspected extraction can be reconstructed and scoped after the fact. You are trading a little answer richness for a large reduction in how cleanly a corpus can be rebuilt from your outputs.

// NOTE

This is exactly the class of exposure our RAG perimeter and AI risk-assessment engagements are built to find — we run the extraction agent against your own endpoint, measure how much of your corpus it recovers, and hand back the pre-retrieval scoping and disclosure-metering controls that close it. If you run RAG over anything you would not publish, assume it is enumerable until you have measured otherwise.

// Related briefings