Three places a filter can live
Every AI data room applies a permission filter somewhere. Vendors describe all three the same way, so the wording tells you nothing and the layer tells you everything.
- Inside the retrieval queryenforced
The permission check is a predicate in the WHERE clause. An unauthorised chunk is never selected, so there is no downstream code path that could return it.
- After retrieval, in application codebypassable
The rows were already read. Every new caller, every error branch, every log line and every cache is a place the unfiltered result can escape through.
- As an instruction in the promptbypassable
The restricted text is already in the context window. Asking a probabilistic system not to use it is a preference, not a boundary.
Placement is half of the control. The other half is the identifier the predicate checks: it has to come from the server, derived from the session, and the function has to be unreachable by any caller who could supply a different one. Both halves have to hold.
How Sifrsys builds it
The subject is rag_vector_search, the function every chat answer retrieves through. Its permission logic is two predicates in one WHERE clause:
WHERE c.tenant_id = p_tenant_id AND d.deleted_at IS NULL AND d.status = 'ready' AND (d.ai_chat_enabled IS NOT FALSE) AND participant_can_access_document(p_participant_id, c.document_id) -- similarity threshold, ordering and limit follow
participant_can_access_document resolves the participant's group defaults, walks up the folder tree for inherited folder permissions, then applies any document-level override, and returns true only at view level or above. The tenant predicate is separate and deliberate: it holds even if the permission resolver is wrong.
- QuestionParticipant identity comes from the session, never the request body
- EmbedQuery vector only, no document content
- RetrieveTenant and participant predicates inside the SQL
- AnswerModel sees authorised chunks only
- CitePage or cell range on every claim
What each caller receives
| Criterion | What it receives | Enforced by |
|---|---|---|
| The room's own participant | Passages from documents they may open | Permission predicate in the query |
| A participant of another room | Nothing from this room | Permission predicate in the query |
| A participant of another organisation | Nothing from this organisation | Tenant predicate in the query |
| A direct call with the public browser key | Call refused | Execute permission revoked for client roles |
The last row is what makes the predicate trustworthy. Direct execute permission is revoked from every client-reachable role, so the only way to reach the function is through a server that derives the participant from the session.
One consequence worth naming. Because the fence is a predicate rather than a post-filter, an answer can only cite what the participant could already open, which is why every citation is checkable by the person who received it. That is covered in the citations piece.
Related: What a citation has to point at · Do watermarks survive screenshots · Pricing
Permission-fenced AI with cited answers is included in every paid tier rather than sold as an add-on. See the tiers.