Data and retrieval

Filtering results is not access control

The moment you copy content into a search index you have created a second access control surface, and it does not inherit the first one.

Open Dimension AI7 min read

How do you enforce permissions in a RAG system?

Enforce permissions before retrieval, not after. Resolve the user's identity, then search only the documents they are entitled to see. Filtering results afterwards still means the model received content the user cannot access, and a model that has read something can be argued into repeating it.

The short version

  • Copying content into a search index creates a second access control surface, and it does not inherit the permissions of the first one.
  • Filtering results after retrieval is a display control. The content still reached the model.
  • Permissions change after indexing, so an index that captured them once is wrong within weeks.
  • Chunking separates a document from the markings that restricted it, so a confidentiality header can end up in a different chunk from the text it governs.
  • The test is not whether the right answer comes back. It is whether the wrong document is reachable at all.

What happens to permissions when you copy content into an index?

They stop applying. A document in SharePoint or a matter folder carries an access control list that the host system enforces on every read. When that document is extracted, split and written into a search index, what arrives is text and a vector. The access control list is not part of the payload unless somebody deliberately carries it across and keeps it current.

This is the part teams miss, and they miss it because nothing fails. The index builds, the search works, the answers look right. You have simply created a second copy of the organisation's content, governed by whatever rules the index enforces, which at that point is usually none.

OWASP names this directly in its 2025 list for LLM applications, under vector and embedding weaknesses: inadequate or misaligned access controls can lead to unauthorised access to embeddings containing sensitive information. Its recommended mitigation is a permission-aware vector store with strict logical partitioning between user groups, not a filter applied to the output.

Why is filtering the results not the same as access control?

Because the retrieval already happened. If the search returns ten passages and the application drops the three the user is not entitled to, the other seven went to the model along with the three, or the three went and were discarded afterwards. Either way the content crossed a boundary it should not have crossed, and the only thing standing between it and the user is a piece of application code.

That code has to be correct in every path, forever. The streaming path, the citation list, the debug view somebody adds next quarter, the export, the evaluation harness. A permission check that runs before the search cannot be forgotten by a later feature, because there is nothing to forget. A filter applied after it has to be remembered every time.

There is a second problem specific to language models. A model that has received a passage can be led into repeating it, paraphrasing it, or confirming its existence. Prompt injection makes this worse rather than hypothetical, and OWASP's own position is that no fool-proof prevention for it is known. Designing on the assumption that the model will keep a secret it was handed is designing on an assumption nobody can defend.

What goes wrong when permissions change after indexing?

People leave teams. Matters close. A contractor's access is revoked. An employee moves from one department to another and should lose sight of the first one. Every one of those events updates the access control list in the source system immediately, and updates nothing in the index.

An index that captured permissions at ingestion is a photograph of who had access on the day it ran. The gap widens every week. In a law firm, where access is scoped per matter and matters open and close constantly, the gap is measured in days.

The design question is not whether the permission data is available. SharePoint and Google Drive both expose it. The question is how often you reconcile, what happens between reconciliations, and whether identity is resolved live at query time rather than read from a stale copy. Resolving it live is more work and it is the only version that stays correct.

What does chunking do to a document's access markings?

It separates them from the text they govern. A memo marked privileged and confidential in a header, or a board paper whose restriction is stated on its cover page, gets split into passages. The passage carrying the marking is one chunk. The passage carrying the sensitive content is another, and it carries no marking at all.

Any downstream rule that looks for the word confidential in the retrieved text will therefore protect the chunk that says confidential and miss the chunk that matters. Markings have to be attached as metadata to every chunk derived from a document, at ingestion, as a property of the chunk rather than something recoverable from its text.

The same reasoning applies to everything else derived from a restricted document. Titles, summaries, and suggested questions are often generated on a different path from the body, and they frequently escape the controls applied to it.

Where should the permission check actually run?

At retrieval, as a constraint on the search rather than a review of its output. In practice that means the query carries the identity of the person asking, the index supports filtering on the access metadata attached to every chunk, and the search is scoped before ranking rather than after it.

The property this buys you is simple to state and hard to get any other way: the model cannot leak what it never received. Everything downstream, including the logs, the traces and the cache, inherits that property automatically, because the content was never in the response to begin with.

It also means the permission model has to be understood before the index is designed. That sequencing is the real cost of doing this properly, and it is why it is usually skipped. Retrofitting it means rebuilding the index.

How do you test this, and what does a passing test look like?

Create a test user with deliberately narrow permissions and a document that user must never see. Then stop asking whether the right answer comes back, and start asking whether the wrong document is reachable at all.

The useful test is adversarial rather than functional. Ask for the document by name. Ask for a summary of the matter it belongs to. Ask what documents exist about the subject. Ask the question whose only correct answer is inside the restricted file, and check whether the response is a refusal or a paraphrase. Check the citation list, the source panel and any search suggestions, because those render document names on a different path from the answer.

Then put those cases in the regression suite, because this is not a thing you verify once. Every new retrieval surface, every new export, every new feature that renders a source is a new opportunity to bypass the check.

What a permission test actually covers

  • Direct request for a restricted document by name
  • A question whose only correct answer lives inside it
  • A request for a list of what exists on a subject
  • The citation list and the source panel, not just the answer text
  • Search suggestions and autocomplete, which usually index titles separately
  • The same set again after a permission is revoked in the source system

What does this cost?

At runtime, very little. Resolving identity and scoping the query costs a fraction of the model call that follows, and the search is usually faster for having a smaller candidate set.

At build time it costs more, because the permission model has to be mapped before the index is designed instead of discovered afterwards. On an estate where access is inconsistent, that mapping exercise often turns up problems that predate any AI project. That is uncomfortable and it is also the point: an assistant does not create the access control problem, it makes the existing one legible.

What does this look like in a law firm or a clinic?

In a firm, access is scoped per matter, and the ethical walls that separate one matter from another are not a preference. An assistant that indexes everything and answers everyone will eventually surface prior work product from a matter the person asking is walled off from, and it will do it in a way that looks like a helpful search result.

In a practice, the boundary is usually between clinical and administrative staff, and the content is patient data. The same mechanism applies and the consequences of getting it wrong are regulatory rather than merely embarrassing.

In both cases the honest answer sometimes is that it is not worth building yet. If five people all have access to everything, there is no boundary to enforce and adding one is cost without benefit. The point at which it stops being optional is the first document one of those five is not supposed to read, and that arrives earlier than people expect.

Questions people ask

Can we just filter the results after the search?

You can, and it will look correct in a demo. The problem is that retrieval already happened and the content already reached the model, so you are relying on the filter being applied in every code path forever. A permission check that runs before the search cannot be forgotten by a later feature, because there is nothing to forget.

Does this work with SharePoint and Google Drive permissions?

Both expose the access control information, so identity can be resolved and the search scoped. The work is in keeping it current. Permissions change constantly, and an index that captured them once at ingestion will be wrong within weeks. The design question is how often you reconcile, not whether the data is available.

What about a document that is restricted but whose title is not?

This is the common failure and it is worth testing for specifically. Metadata, titles and summaries often get indexed on a different path from the body, so a restricted document can be invisible in the answer and perfectly visible in a source list or a search suggestion. Treat every surface that renders a document name as a retrieval surface.

Does the permission check slow the system down?

It adds the cost of resolving identity and scoping the query, which is small against the model call that follows. The larger cost is in build time, because the permission model has to be understood before the index is designed rather than after. That sequencing is the actual expense, and it is the reason this gets skipped.

Do we need this if the assistant is only used by five people?

Sometimes not, and we will say so. If all five already have access to everything, there is no boundary to enforce and adding one is cost without benefit. The point at which it stops being optional is the first document that one of those five is not supposed to read, and that usually arrives earlier than people expect.

Related on this site

Sources

  1. LLM08:2025 Vector and Embedding Weaknesses · OWASP Gen AI Security Project, data from 2025
  2. LLM02:2025 Sensitive Information Disclosure · OWASP Gen AI Security Project, data from 2025

Want someone to look at your workflow?

Thirty minutes, and we will tell you whether there is anything here worth building. Roughly half of these end with us saying not yet.

Book a 30 minute call