Document-level access control in RAG is implemented by attaching permission metadata to every chunk at ingestion time and filtering retrieval results against the requesting user's permissions before any chunk reaches the language model, not after. Each chunk stores identifiers such as the source document's access control list, department, or classification level alongside its vector, and the retrieval query includes a metadata filter derived from the user's identity and group membership, so a search only returns chunks the user is actually authorized to see, exactly mirroring the access rules already enforced in source systems like SharePoint or Confluence. This must happen at the vector database query level, not as a post-processing filter applied to results after retrieval, because filtering after the fact can still leak information through the language model referencing unauthorized content indirectly. Permissions should sync continuously from the source system, since a document made confidential in SharePoint needs the corresponding chunks re-tagged or removed from eligible results without a lag that leaves temporarily authorized answers reachable. Audit logging of which documents were retrieved for which query and which user adds accountability that regulated industries typically require. Nanobase AI builds permission-aware retrieval as a first-class part of the architecture rather than bolting access control on afterward.
The order of operations is the whole problem
Most descriptions of RAG access control stop at "attach permissions to chunks and filter by them," which is correct but skips the detail that actually determines whether the implementation is safe: whether filtering happens before or after the vector search selects its top-k candidates. Post-filtering, running the similarity search first and then removing unauthorized chunks from the returned results, is the more common mistake, because if the top-k candidates returned before filtering happen to be dominated by chunks the user cannot see, the final filtered result set can end up empty or missing an authorized chunk that ranked just below the cutoff.
Filtering after the top-k cutoff, rather than before it, is the most common way document-level access control silently degrades retrieval quality for authorized users.
Pre-filter vs post-filter compared
| Approach | How it works | Risk |
|---|---|---|
| Post-filter | Retrieve top-k, then remove unauthorized results | Can return fewer than k results, or miss an authorized chunk ranked just outside the initial cutoff |
| Pre-filter | Apply the permission filter as part of the similarity search itself, so only authorized chunks are candidates | Requires the vector database to support efficient filtered search without a large recall penalty |
| Over-fetch and filter | Retrieve a much larger candidate set than needed, then filter down to k | Mitigates the post-filter problem at the cost of extra retrieval compute |
Native pre-filtering, where the vector database applies the permission filter during the search itself rather than afterward, is the correct default when the database supports it efficiently.
What the permission metadata schema needs to capture
- A stable identifier connecting each chunk back to its source document's access control list, not a copy of the ACL itself, since permissions can change after ingestion.
- Department, classification level, or project association, if access is scoped along those dimensions rather than a simple per-document ACL.
- An effective-date or revocation marker, so a permission change takes effect on the next query rather than requiring a full re-index.
- A join or lookup mechanism at query time to resolve the requesting user's current permissions against the stored chunk metadata, rather than baking a snapshot of permissions into the vector store that can go stale.
Storing a reference to the permission system rather than a snapshot of permissions is what keeps access control correct as permissions change after ingestion.
Why this needs testing, not just implementation
Access control in RAG fails silently in a way that functional bugs usually do not: an authorized user occasionally getting a poor answer because of the post-filtering problem looks like a retrieval quality issue, not a security issue, and an unauthorized user seeing content they should not is a breach that may go unnoticed without explicit testing. A dedicated test suite that simulates different user permission levels against the same query and confirms both correct exclusion and complete authorized results is a necessary addition beyond the initial implementation.
Access control bugs in RAG present as quality problems or invisible breaches, not crashes, which is why they need dedicated tests rather than being assumed correct once implemented.
Frequently asked questions
Does every vector database support efficient pre-filtered search?
Not equally well. Qdrant and Milvus both support payload or attribute filtering integrated into the similarity search itself; pgvector supports filtering through standard SQL, though very selective filters combined with approximate indexes can reduce recall, which is worth testing directly.
How do we handle a document whose access level changes after it has been indexed?
Update the permission metadata associated with the chunk's stable document identifier rather than re-embedding the content, since only the access attribute changed, not the text; the next query then reflects the updated permission immediately if metadata is resolved at query time.
Can row-level security in PostgreSQL be used for RAG access control with pgvector?
Yes, PostgreSQL's row-level security features can enforce access control directly at the database layer for pgvector-backed retrieval, which is a reasonable approach for teams already standardized on Postgres for this kind of policy enforcement.
Should access control be enforced in the application layer or the database layer?
Enforcing it as close to the data as possible, ideally in the vector database's filtered search itself, is safer than relying solely on application-layer logic, since a bug in application code should not be the only barrier preventing an unauthorized chunk from reaching a user.
How Nanobase AI helps
Nanobase AI implements document-level access control in RAG with native pre-filtering wherever the underlying vector database supports it, and builds a dedicated test suite simulating multiple permission levels before a system goes live, treating access control as a security requirement rather than a metadata afterthought. See our AI security and compliance work and the EU AI Act, GDPR and KVKK compliance checklist for the surrounding regulatory context.
Ready to discuss your project? Contact Nanobase AI or email hello@bumu.tech.