Permission-aware retrieval: the security question enterprise AI keeps getting wrong
The Curiosity Team
Every enterprise AI project reaches the same uncomfortable moment. The assistant works. It answers questions from company data, cites its sources, and people start to trust it. Then someone asks: what stops it from telling me something I'm not allowed to see?
That question rarely has a good answer, because permissions were an afterthought. The data was indexed, the model was wired up, and access control was added at the end, usually as a filter on the way out.
Where the leak happens
The common design looks reasonable. The assistant retrieves whatever is relevant, the model writes an answer, and a check at the end hides any source the user wasn't cleared for. Sources they can't access don't appear in the citations.
By the time you filter the citations, the model has already read the restricted document. It reasoned over the salary figure, the unreleased financials, the HR note, and folded them into the summary. Hiding the link to the source does not un-say the sentence the model wrote from it.
An employee asks the assistant to summarize "everything we know about the Q3 reorg". Retrieval pulls in a restricted planning document. The citation is stripped on the way out, so the answer looks clean, but the summary still mentions the two roles being cut, because the model saw them.
Permissions belong to the data, not the response
The fix is to move the check upstream. The model should never retrieve a record the user cannot access, so there is nothing to leak, redact or reason over by accident.
That only works if permissions live with the data instead of in a separate layer that runs last. Every record carries who can see it. Every query is made on behalf of a specific user. Retrieval resolves the two together and returns only what that user is entitled to, so the model works from the same view the user would get if they searched by hand.
How this works when permissions live in the graph
"Move the check upstream" is simple to say. Most stacks keep permissions in one system and the searchable index in another, so the two have to be reconciled by whatever code runs last.
In Curiosity, permissions are relationships in the same graph that holds the data. Documents, users and teams are all nodes. Access is a path between them: a user belongs to a team, the team owns the record, so the user can see it. There is no second database of rules to keep in sync, because the rule is an edge in the graph.
Those edges are written when the data arrives, not after. If a source system already has its own permissions, they are mirrored at ingest time rather than reconstructed later. That timing matters: bolt ACLs on after indexing and you leave a window where the content is searchable but unprotected.
Enforcement then happens at query time, on every path into the data. When a user searches, the engine compiles that user's team memberships into an access filter and applies it before ranking, so restricted records never enter the candidate set. Direct fetches by ID go through the same check. Because the AI is grounded in that same filtered retrieval, the model is handed only what the user could already see. One rule, enforced in one place, for search, direct lookups and AI answers.
It is also a correctness problem
Leaks are the obvious risk. The other one is that ignoring permissions makes the AI wrong.
An assistant that retrieves across everything, for everyone, answers as if every user were an administrator. Ask it "how many open reqs does my team have?" and it counts reqs the asker can't see, so the number is confidently, precisely wrong. Ask "what's the status of the Acme deal?" and it summarizes a version of the deal the asker isn't on, complete with a discount they were never supposed to know about. In both cases the answer reads as authoritative and describes a company the user does not work in.
Permission-aware retrieval fixes both with one mechanism. The user gets answers grounded in the data they are allowed to use, which is also the only data that produces a correct answer for them.
What this looks like in practice
Getting this right means enforcing access in a few places that are easy to forget:
- At the field, not only the document. Sometimes a record is fine to see but one field on it is not: a salary column, a customer's contract terms. Access control has to reach individual fields and record types.
- Everywhere data is read back. Not only the AI answer, but search results, facet counts, and autocomplete. A search that returns "0 results, 3 hidden" has told the user three things exist, so the filter has to apply before the count is computed, not after.
- On the tools the assistant can call. An assistant that can run actions needs those actions scoped too. A tool a user can't access should be invisible to their assistant, not merely refused when called, since a refusal still confirms the tool exists.
- With a record of what happened. When retrieval respects permissions, you can also prove it did: an audit log of who asked what, and what the system returned.
None of this is exotic. It is the access model the rest of the business already runs on, applied at the moment the AI reads data instead of after it speaks.
The takeaway
If you are evaluating an enterprise AI system, do not ask whether it "supports permissions". Ask two things: where do the permissions live, and when are they applied. If they live in a layer on top of the index and are applied as a filter at the end, the model is still reading things it should not.
The version that holds up in production keeps permissions in the data layer and enforces them everywhere the data is read: search, facets, tools and AI answers, resolved per user at query time. That is how Curiosity is built. Permissions live on the graph, retrieval resolves them for the person asking, and the assistant only sees what that person could see themselves.
To see how it works on your own data and access model, talk to an engineer.
Read next
Articles on context graphs, enterprise search and industrial AI