Document-Level Access Control Lists for AI Retrieval Systems

TL;DR — Document-level ACLs enforce per-document permissions in RAG systems, ensuring users only retrieve documents they are authorized to access. Two approaches: pre-filter (query permission system before vector search) and post-filter (check permissions on results). pgvector supports RLS for database-level enforcement. Metadata filtering in vector stores (Pinecone, Bedrock) enables attribute-based access control. Systems must fail closed — denying access when permissions cannot be verified. Without document-level ACLs, RAG retrieval exposes all indexed documents to every user.

The vector database usually lacks the complex Access Control Lists found in systems like SharePoint, Google Drive, or Salesforce. When you index documents from these sources into a RAG pipeline, the permissions that existed in the source system disappear. A vector search returns the most semantically similar chunks regardless of who owns them or who has access (Quellix Labs 2026).

This creates a critical security gap. An engineer asking the AI about "salary bands" could retrieve HR documents they have no permission to see. A sales rep asking about "Q3 financials" could access confidential board reports. The AI happily generates responses using documents the user should never see.

Document-level ACLs close this gap by enforcing per-document permissions at the retrieval layer. This post covers the architecture, implementation patterns, and failure modes. For tenant isolation at the platform level, see our companion post.

The RAG Permission Problem

When documents are ingested into a vector store for RAG, they are typically:
1. Chunked into smaller passages
2. Embedded into vectors
3. Stored with metadata (source, title, date)

What is often missing: the access control metadata from the source system. SharePoint documents have permissions. Google Drive files have sharing settings. Salesforce records have role-based visibility. None of this survives the ingestion pipeline unless you explicitly capture and enforce it.

flowchart TD Source[Source System
SharePoint, Drive, CRM] -->|has permissions| ACLs[Document ACLs] Source -->|ingestion pipeline| Chunk[Chunking] Chunk --> Embed[Embedding] Embed --> Vector[Vector Store] Vector -->|missing ACLs| Query[User Query] Query -->|returns ALL matches| LLM[LLM] LLM -->|exposes unauthorized data| Response[Response] ACLs -.->|must be carried through| Vector

The fix: capture ACLs at ingestion time, store them as metadata in the vector store, and enforce them at query time.

Two Approaches to ACL Enforcement

The permission system is queried first to get the list of document IDs the user can access. Only those documents are searched in the vector store.

Aspect Pre-filter
How it works Get authorized document IDs → search only those in vector store
Efficiency Good for large corpora with low hit rates
Latency Permission lookup adds overhead before search
Tools SpiceDB LookupResources, Cerbos Query Plan

Post-filter: Check permissions after vector search

The vector store returns the top-K results. Each result's document ID is checked against the permission system. Only authorized results are passed to the LLM.

Aspect Post-filter
How it works Vector search → check permissions on each result
Efficiency Good for high hit rates (most results are authorized)
Latency Permission checks add overhead after search
Risk May return zero authorized results if top-K are all unauthorized
Tools SpiceDB CheckPermission, Cerbos resource checks

When to use which: If you have a large corpus and users typically have access to a small subset, pre-filter is more efficient. If most users have access to most documents, post-filter is faster (Pinecone 2026).

Implementation With pgvector and Row-Level Security

Since pgvector runs on PostgreSQL, you can use Row Level Security (RLS) to enforce document permissions at the database level. This is the strongest enforcement model — the database itself rejects unauthorized rows.

Step What it does
1. Store ACLs Each document chunk has an owner_id or allowed_groups field
2. Create RLS policy Database policy filters rows based on current user identity
3. Set session context Application sets app.current_user_id per request
4. Vector search respects RLS Similarity search automatically filters to authorized rows

With RLS, every SELECT query on the vector table — including similarity searches — implicitly filters to documents the current user can access. The application cannot bypass this even with a bug (Supabase 2026).

Implementation With Metadata Filtering

Vector databases that support metadata filtering (Pinecone, AWS Bedrock Knowledge Bases, Weaviate) allow you to attach permission attributes to each embedding and filter at query time.

Vector store Filter mechanism Enforcement level
pgvector Row Level Security (RLS) Database-level (strongest)
Pinecone Metadata filter on query Application-level
AWS Bedrock Metadata filter via RetrieveAndGenerate API Application-level
Weaviate Where filter with operator conditions Application-level
Chroma Where filter with metadata conditions Application-level

The critical difference: database-level enforcement (pgvector RLS) cannot be bypassed by application bugs. Application-level enforcement (metadata filtering) depends on the application correctly constructing the filter every time. A single missing filter in one code path exposes all documents.

External Policy Decision Points

For complex permission models, use an external Policy Decision Point (PDP) rather than embedding permission logic in your application:

PDP How it works Integration
Cerbos Policy DSL defines resource permissions Query Plan generates vector store filters
SpiceDB Google Zanzibar-inspired relationship-based ACLs LookupResources or CheckPermission API
AWS Verified Permissions Cedar policy language Converts policy decisions to metadata filters
OPA (Open Policy Agent) Rego policy rules External evaluation, filter construction in app

The PDP evaluates the user's permissions and returns a filter expression that the application applies to the vector store query. This separates permission logic from application code and ensures consistent enforcement (Cerbos 2026).

Fail-Open vs Fail-Closed

The most dangerous failure mode in ACL-enforced RAG is fail-open: when the permission check fails or times out, the system returns all documents instead of none.

Failure mode What happens Risk
Fail-open Permission check fails → return all results Critical: exposes all documents
Fail-closed Permission check fails → return no results Safe: user sees nothing, reports issue

Always fail closed. If the PDP is unreachable, if the metadata filter cannot be constructed, or if the permission system times out, deny access. A user who sees nothing will report a bug. A user who sees everything may not.

ACL Synchronization

Permissions change. Users gain and lose access. Documents are reclassified. The ACL enforcement system must stay synchronized with the source system:

Sync strategy How it works Tradeoff
Real-time sync Webhook from source system updates vector store metadata Most accurate, complex to implement
Scheduled sync Batch job reads source ACLs and updates vector store Simpler, risk of stale permissions
On-demand check Query source system at retrieval time Always current, adds latency
Hybrid Scheduled sync + on-demand for high-value documents Balanced approach

For PII redaction pipelines, ACL metadata should be captured at ingestion time alongside the redaction process, ensuring both privacy and access controls are enforced from the moment data enters the system.

FAQ

What are document-level ACLs in AI retrieval systems?

Document-level ACLs (Access Control Lists) enforce per-document permissions in RAG systems. They ensure that users can only retrieve and use documents they are authorized to access, preventing unauthorized data exposure through AI-generated responses.

How do you implement access control in a vector database?

Two main approaches: pre-filter (query the permission system for authorized document IDs before searching the vector store) and post-filter (search the vector store, then check permissions on each result). Pre-filter is efficient for large corpora with low hit rates; post-filter works well for high hit rates.

Does pgvector support row-level security for RAG?

Yes. Since pgvector runs on PostgreSQL, you can use Row Level Security (RLS) policies to restrict which document chunks are returned during vector similarity search. RLS policies implicitly filter results based on the current user's identity.

What is metadata filtering in vector databases?

Metadata filtering attaches attributes (department, user role, sensitivity level) to vector embeddings. At query time, the vector search applies these filters so only documents matching the user's permissions are retrieved. AWS Bedrock Knowledge Bases and Pinecone both support this.

What happens if ACL filtering fails open in a RAG system?

If the middleware that constructs the metadata filter fails to enforce permissions, documents from all users or tenants are exposed in the retrieval results. This is a critical security failure. The system must fail closed — denying access when the filter cannot be verified.


Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →