Six weeks after Kestrel Financial launched its internal assistant, someone in finance asked it about this year's bonus pools and got a precise answer, drawn from a compensation planning spreadsheet that six people in HR were supposed to be able to open. The citation link, when clicked, returned access denied. The assistant had read the file anyway. Kestrel is a fictional lender, and its ingestion job ran as a service account a platform engineer had granted read access to every drive, so indexing would not fail on permission errors, and it stored each chunk with a source URL and a title but not who could open the file.
A cited source the reader cannot open is the signature of this failure. The source system enforced its permissions perfectly; the assistant was not asking it. The argument of this piece is that the permission check belongs in exactly one place, inside the retrieval query, built on the server from the caller's verified identity, and that every other placement leaks something. There are two serious objections. One loses outright; the other changes how the check has to be built, not where it goes.
The index runs with the indexer's access
An indexer has to read documents to index them, so it runs under an account with broad read access to the wiki, the shared drives and the ticketing system. Each person who queries the assistant has a small, specific subset of that access. If per-document permissions do not travel from the source into the index, every query runs with the indexer's access, not the reader's. Kestrel's pipeline retrieved the twenty most similar chunks from the whole index, reranked them and passed the top eight to the model. Every component behaved as designed, and no step asked whether the person could read the source.
OWASP's LLM08:2025, Vector and Embedding Weaknesses, names the risk directly: in a vector database shared by several groups, embeddings from one group can be retrieved for another group's queries, leaking sensitive business information. It recommends fine-grained access controls, permission-aware vector stores, and strict logical and access partitioning of datasets [1]. None of that happens by default when a pipeline chunks text, embeds it and writes vectors.
The claim: if the filter is not in the query, something downstream has already read the text
Teams that discover the problem usually add a check, and where they add it matters more than how good it is. There are three common placements, and only one keeps restricted text away from every downstream component.
Filtering the answer is too late because the model has already read the text. Filtering in the application after retrieval still exposes candidates to rerankers, logs and caches. Filtering inside the retrieval query means unauthorized chunks never leave the store.
Output filtering asks the model, or a classifier after it, to withhold what it should not have seen. That is a disclosure control applied after the disclosure, and a summary or paraphrase can carry the restricted fact without matching any pattern. It fails on its own terms.
Objection one: filtering after retrieval is simpler and nearly as good
This is the serious objection. Retrieve globally, then drop the chunks the caller cannot read before they reach the model. It needs no change to the vector store and keeps the permission logic in application code where the team already works.
It loses on two counts. By the time the filter runs, the candidates have passed through the reranker, any debug logging and any trace exporter, which are copies of restricted text in places with their own readers. And when the filter removes most of the top results, the answer degrades, which tempts developers to raise the retrieval limit or switch the filter off. A design whose failure mode invites people to disable it is not nearly as good. Put the predicate in the query itself, so unauthorized chunks never leave the store.
Objection two: a metadata filter is only a string
This one is true, and it changes how the claim has to be built rather than whether it holds. Microsoft's documentation for Azure AI Search describes the security filter pattern: store group or user identifiers in a filterable field on each document, then filter every query by the caller's groups [2]. It is candid about the limits. The principal in that field is a string used in a filter expression, with no authentication or authorization behind it; making the field non-retrievable only keeps it out of results and is not field-level security; and authorization is enforced only by applying the filter to every query [2]. The same is true of every vector store with metadata filtering. The filter is exactly as trustworthy as the identity your application puts into it, so the identity is where the rest of the work goes.
Each chunk needs enough metadata to decide access without going back to the source: parent document ID, source system, tenant, allowed principals (users and groups), the time permissions were last synchronized, and a classification label if you use one. The chunk stays linked to its parent, so a permission change or deletion reaches every chunk derived from it.
- Store group IDs, not expanded user lists, and resolve the caller's groups at query time from your identity provider. Expanding at ingestion freezes membership: new joiners cannot find documents and people who left a team still can.
- Resolve inherited permissions. Effective readers often come from a folder, space or project, and a connector that reads only the file's own sharing entries misses them.
- Decide explicitly how to treat link and organization-wide sharing. Anyone with the link is not everyone in the company, and indexing it as public turns an obscure link into a search result.
- Fail closed on missing metadata. A chunk with no permission data is readable by no one until the connector fills it in.
The filter's inputs, the user, their tenant and their current groups, come from the authenticated session, resolved on the server, never from the request body, a query parameter or the model. A retrieval tool an agent calls should not accept a user or groups argument at all. A model that can choose whose permissions to search with will eventually be asked to. The OWASP Authorization Cheat Sheet's rule applies unchanged: never rely on client-side access control checks as the decisive factor [3].
Check the side doors. Many assistants have a second tool that fetches a full document by ID after a search returns a snippet. If it does not repeat the check, anyone who can guess or harvest an ID can read the document, the insecure direct object reference problem the cheat sheet addresses by requiring access control checks on every request for the specific object [3]. If the same index also serves an API or a plugin, a filter applied only in the chat interface protects only the chat interface. When you serve more than one customer, partition by tenant at the storage level so a bug in the filter cannot cross a tenant boundary [1].
| Stage | What must carry the permission | Typical failure | Test that catches it |
|---|---|---|---|
| Ingestion | Each chunk's parent ID, allowed principals and sync time | Connector drops sharing data or ignores inherited permissions | Index a document shared with one test group; inspect its chunks' metadata |
| Retrieval | A filter built from the caller's verified groups, inside the query | Global top-k search, filtered afterwards or not at all | Ask a question only a restricted document answers, as an unauthorized user |
| Fetch by ID | The same check, repeated for the specific document | Full-document tool trusts the ID from the search result | Call the fetch tool directly with a restricted document's ID |
| Reranking and logs | Only already-filtered candidates | Debug logging or traces capture every candidate | Search the trace store for a canary phrase after a denied query |
| Answer cache | Key includes the caller's permission set | Semantic cache serves one user's answer to another | Ask the same question as two users with different access |
| Summaries and memory | The intersection of every source's readers | Precomputed digest readable by everyone in the workspace | Revoke access to one source of a digest and query the digest |
The same argument applies to every copy of the answer
Anything that stores retrieved text or a generated answer is a copy of the restricted data and needs its own access rule. Semantic caches are the most common leak: they return a stored answer for any question that is similar enough, so unless the key includes the caller's permission set, the first person to ask decides what everyone else sees. Key the cache by a hash of the caller's groups and the sources' permission revision, or cache retrieved document IDs and re-filter on every hit. Retrieved passages logged in full to an observability platform every engineer can search are a copy too.
Summaries and digests combine sources, so their readers are the intersection of every source's readers, not the union and not the readers of the space where the digest is stored. Conversation memory is a copy: a person who loses access to a project should not keep getting answers from a summary the assistant wrote for them last month. Titles and counts leak as well. An assistant that confirms a restricted document exists while withholding its contents has still disclosed something. Decide how long each copy lives and what invalidates it, next to the source systems' own retention rules.
Revocation is where the claim gets measured
When access is removed at the source, the assistant keeps answering until every copy catches up. The exposure window is the slowest copy, not the fastest. Measure it, publish it internally, and set a target for sensitive collections.
Access is removed at the source first; the index permissions, caches, summaries and memory each catch up on their own schedule. The exposure window ends only when the slowest copy does, so measure each one.
Deletion has the same shape plus one fact: vectors are not anonymous. LLM08 notes that attackers can invert embeddings and recover significant amounts of the source information [1]. Deleting a document's text while its embeddings stay in the index is not deletion. Remove the chunks, vectors, cached answers and passages, and summaries derived from the document, and keep whatever audit evidence you need under a separate, access-controlled policy.
The most reliable test is a canary document: a file containing a unique, harmless phrase, shared with one test account. Confirm the account can retrieve it, revoke access at the source, and record how long until search, citations, cached answers and follow-up questions all stop returning the phrase. Repeat for removal from a group and for deletion of the file, then check the vector store for the canary's embeddings. If the connector's sync fails, quarantine stale chunks from sensitive collections until it recovers rather than serving them on old permissions.
What the claim does not cover: who can write to the index
Access control decides who may read a document. It says nothing about whether the document is true or carries instructions aimed at the model. PoisonedRAG, presented at USENIX Security 2025, reports a 90% attack success rate when five malicious texts are injected per target question into a knowledge base of millions of texts, and found the defenses it evaluated insufficient [4]. That describes the researchers' setup, not the compromise rate of any particular assistant, but it shows why write access to the corpus matters as much as read access. List who can write into each indexed source, including external parties who file tickets or comment on shared pages. LLM08 recommends accepting data only from trusted, verified sources and auditing the knowledge base for hidden content and poisoning [1].
Treat retrieved text as untrusted input, and keep any consequential action the assistant can take under a policy that does not depend on what a retrieved document says. Intertrace's gateway sits inline between an assistant and the models, tools and MCP servers it calls, so retrieval tool calls and model responses pass one point of enforcement: tenancy from the runtime key rather than the request body, PII and secrets detection and redaction on requests and responses, DLP over tool-call arguments, and a structured event per decision for your SIEM. It does not know your document permissions. Permission-aware retrieval, built from your source ACLs and identity provider, is a change only your retrieval service can make.