The most common serious flaw in RAG systems, and it is an authorisation bug wearing a machine learning costume.
The indexer runs with broad permissions, because it has to read everything to index it. Retrieval then queries the index. If retrieval does not re-apply the caller's permissions, every user can reach every document, and the similarity search will happily surface the most relevant one regardless of who owns it.
public async Task<IReadOnlyList<Chunk>> RetrieveAsync(
string query, ClaimsPrincipal caller, CancellationToken ct)
{
var filter = new IndexFilter(
Tenant: caller.TenantId(),
VisibleTo: await _authz.VisibleSourceIdsAsync(caller, ct));
// Filter is applied in the query, not after. Post-filtering
// leaks through result counts and latency.
return await _index.SearchAsync(query, filter, topK: 8, ct);
}Apply the filter inside the search rather than after it. Post-filtering leaks: the number of results removed, the latency profile, and the ranking behaviour all tell an observer something about documents they cannot see.
In multi-tenant systems, inadequate partitioning causes cross-context retrieval, where one tenant's query surfaces another tenant's embeddings. Where the risk justifies it, separate indexes per tenant are stronger than a shared index with a filter, because a filter is a line of code that can be omitted and an index boundary is not.
An agent composes the arguments it passes to your tools. That text did not come from your code, it came from a model, and its contents are not predictable.
This is structurally different from every leak so far. A prompt is a string you built. A tool call is a string the model built, possibly under the influence of text an attacker controlled.
The research is unambiguous. Simple prompt injection has been shown to leak personal data that agents observed during ordinary task execution, and data exfiltration via backdoored tool use is a demonstrated technique. Guardrail classifiers placed in front of the model reduce the success rate of malicious prompts materially, and do not eliminate it. This book does not quote a figure for that reduction, because the numbers in circulation trace to vendor testing rather than to a primary result that could be checked.
Three controls, in order of how much they buy you.
Capability design. Limit what the agent can do so that a successful injection has a small blast radius. An agent that can read customer records and send email is an exfiltration tool with extra steps. An agent that can read customer records and draft email for human approval is not. The question is never "can the model be tricked" but "what happens when it is".
Filter the tool-call arguments. Run the same detection pipeline over arguments that you run over prompts. Model-composed text going outward is exactly the case the gateway exists for.
public async Task<ToolResult> InvokeAsync(
ToolCall call, GatewayPolicy policy, CancellationToken ct)
{
if (_tools[call.Name].HasExternalEffect)
{
var inspected = await _detection.ApplyAsync(
call.SerialisedArguments, policy.Outbound, ct);
if (inspected.Findings.Count > 0 && policy.BlockPiiInToolCalls)
throw new ToolCallBlockedException(call.Name, inspected.Findings);
}
return await _tools[call.Name].InvokeAsync(call, ct);
}Audit every call. Tool name, argument shape, finding counts, caller, timestamp. Not the arguments themselves, for the Chapter 13 reason. Without this record, exfiltration through a tool leaves no trace in your prompt logs, because the data never appeared in a completion.
Download the full PDF for free?
Free download — no account required