AI SECURITY
How a RAG System Leaks a Salary Review
A step-by-step look at how an apparently successful RAG system can expose restricted internal documents — and why permission filtering has to happen before retrieval.
Here is a failure that is easy to reason about and easy to build.
An organisation indexes its internal documents so staff can ask questions of them. The retrieval system works well. Within a few days of launch, somebody asks a broad question about compensation and receives, correctly retrieved and helpfully summarised, a fragment of an individual’s salary review.
Nothing malfunctioned. The system did exactly what it was built to do.
The problem is in the architecture, and it is worth walking through precisely, because it is one of the few AI failure modes where the mechanism is completely understood and the fix is well documented.
This is documented, not hypothetical
Two pieces of public record are worth reading before building anything in this space.
The first is Microsoft’s own deployment guidance for Microsoft 365 Copilot, which identifies excessive permissions as a major risk in Copilot rollouts and recommends permission cleanup before broad deployment.
The mechanism they are describing is not necessarily a flaw in Copilot itself. Natural-language retrieval makes content discoverable that may previously have been protected by little more than being difficult to find.
Every organisation has documents in that category. Most have a lot of them.
The second is EchoLeak, catalogued as CVE-2025-32711 and rated 9.3.
Disclosed by researchers at Aim Labs in 2025 and patched server-side by Microsoft, it was a zero-click indirect prompt injection in Microsoft 365 Copilot.
A single crafted email, with instructions hidden in it, could cause Copilot to pull the user’s internal content into an outbound reference and exfiltrate it — with no click and no file opened.
Aim Labs described it as an “LLM scope violation” : the system blended trusted internal sources with untrusted external content without enforcing a boundary between them.
Microsoft reported no in-the-wild exploitation.
The relevant point is not the specific bug, which is fixed. It is that the attack class applies to any assistant that can reach multiple internal sources and emit output somewhere else.
The mechanism, step by step
The walkthrough that follows is constructed to illustrate the architecture. It is not an account of anything we were involved in.
Step one: documents are collected
Documents are collected from a document management system, a wiki, a shared drive and an email archive.
Each source has its own access model, applied over years by different people with different assumptions.
Some of those controls are current and deliberate. Others are residue from a reorganisation nobody has audited since.
Step two: content is extracted, permissions are not
Content is extracted and split into chunks for embedding.
Access control lists are not extracted, because extracting them requires a per-source integration and the content extraction already worked.
This is the decision that causes everything afterwards, and it is almost always made for schedule reasons rather than as a considered risk acceptance.
Step three: everything enters one index
Chunks go into a single index.
At this point the index has no concept of who may see what. Every chunk is equally retrievable by every user who can reach the query interface.
Step four: testing still looks successful
Testing is done against a curated set of a few hundred non-sensitive documents.
Everything behaves.
This is the part that makes the failure so common: the system is genuinely correct throughout development because the corpus it was developed against was safe.
The risk arrives with the real corpus, at the point where the system is connected to real sources and visible to enough people that rolling it back becomes a much larger organisational decision.
Step five: similarity search does exactly what it should
A user asks a general question.
Similarity search does its job, surfaces the most semantically relevant passages, and one of them comes from a performance and compensation document that the user cannot open in SharePoint.
The model summarises it accurately.
The shortcut that looks like a fix
The obvious remedy is to check permissions on the retrieved results and drop the ones the user cannot see.
This is easier to implement, performs better, and does not solve the problem.
By the time filtering happens, the restricted passage has already been loaded into the context assembled for the model.
A model that has read a document can leak its contents through paraphrase, through a summary that does not quote it, or through an inference that references nothing directly.
You have protected the citation list while leaving the actual exposure in place.
What the correct architecture requires
Four things, none of them exotic.
Permissions captured at ingestion
Permissions need to be captured alongside content and carried as metadata on every chunk derived from a document.
This means a per-source integration that extracts ACLs as well as text, which is the work that gets cut and should not be.
A permission resolver
The system needs a permission resolver that maps the requesting user to an effective permission set, expanding group membership transitively.
Nested groups are normal in any organisation old enough to have restructured, and a naive directory walk on every query can dominate latency.
Cache the result with a short TTL and choose the resulting staleness window deliberately.
A pre-filter on the vector search
Restricted content must never enter the candidate set.
Most serious vector stores support filtered search. Azure AI Search documents both filter-based approaches and native ACL preservation.
Its guidance on why long disjunctions of equality expressions degrade badly is worth reading before implementing a large permission filter yourself.
A re-sync job for permission drift
This is the component that gets deferred and the component that causes the incident.
People change teams. Projects close. Contractors leave. A document’s ACL gets tightened long after the document was indexed.
Treat permission drift as a monitored data-quality problem with an alert and an owner, not as a nightly cron job whose output nobody reads.
Test it adversarially, in CI
Build the test set before the system, and run it on every change.
At minimum it needs:
- A user who should see nothing at all in the corpus.
- A user with access to exactly one document out of ten thousand.
- A user whose access to a specific document was revoked after indexing.
- A user whose access comes via a group nested three levels deep.
Each of these is a distinct failure mode with a distinct root cause, and passing three of the four is not passing.
Add one more, in light of EchoLeak: a document in the corpus containing instructions addressed to the model.
Untrusted content must never carry instruction authority. Tool access must be explicitly scoped, and anything with a side effect needs confirmation.
A retrieval system that will follow orders found in the fortieth chunk of a wiki page is a different kind of problem from an over-permissive index, and both can be present at once.
The uncomfortable prerequisite
If your permission model is undocumented or disputed, this architecture will not help you.
It encodes an access model; it does not improve one.
Point it at a permission structure nobody can explain and it will faithfully and efficiently reproduce every error in that structure, while adding a natural-language interface that makes those errors dramatically easier to find.
That is the real reason permission hygiene has to happen first.
It is also the answer to the question of when not to build this at all: if the access model cannot be described, the retrieval project is not the project you need.
Discovering that now is considerably cheaper than discovering it from a user.
The full architecture, with component options, a tradeoff table and implementation notes, is written up as Pattern 1 — Permission-aware retrieval over enterprise documents .
References
-
Document-level access control in Azure AI Search
— the four available approaches, from security filters
through to native ACL preservation.
Microsoft Learn -
Security filters for trimming results in Azure AI Search
— implementation mechanics, including why long chains of
equality expressions can perform badly at scale.
Microsoft Learn -
EchoLeak: the first real-world zero-click prompt injection exploit in a production LLM system
— academic write-up of CVE-2025-32711 with the attack
chain and disclosure timeline.
arXiv -
EchoLeak in Microsoft Copilot: what it means for AI security
— analysis of the LLM scope-violation attack class.
Varonis -
EchoLeak (CVE-2025-32711): what the Copilot prompt injection vulnerability means for your data
— discussion of data-access scoping as a defence against
prompt-injection-driven exposure.
Sentra -
Copilot as a security risk
— discussion of oversharing assessment and permission
cleanup before rollout.
Security Today -
azure-search-openai-demo: login and ACL documentation
— working reference implementation with the token flow
and ACL integration documented.
GitHub
PERMISSION-AWARE RETRIEVAL
Keep restricted content out of the candidate set.
See the full architecture for permission-aware retrieval, including ingestion, ACL metadata, permission resolution, pre-filtered vector search, permission drift and testing.