Deploying Retrieval-Augmented Generation (RAG) across enterprise knowledge repositories introduces a security boundary that simple vector search was never designed to enforce. In corporate environments spanning Google Workspace, Microsoft SharePoint, Notion, Confluence, and internal ticket systems, access permissions are dynamic, hierarchical, and deeply nested.
Attempting to enforce security at the prompt generation layer by instructing language models to ignore unauthorized context is fundamentally broken. Prompt injections, latent attention leakage, and soft context blending can easily bypass conversational instructions. Security in enterprise RAG must operate deterministically at the retrieval and storage tiers: unauthorized document chunks must never enter the model context window.
Implementing fine-grained authorization across vector databases requires balancing retrieval latency, search recall, index fragmentation, and permission synchronization lag.
Access Control Topologies in Vector Retrieval
Enforcing document Access Control Lists (ACLs) in vector search architectures relies on three primary query topologies: metadata pre-filtering, post-filtering authorization, and hybrid two-pass retrieval.

1. Metadata Pre-Filtering (Pushdown Index Filtering)
In metadata pre-filtering, every document chunk stored in the vector database is tagged with access identifiers (such as user IDs, team IDs, or role tags). When a user issues a query, the application extracts the user's identity claims and injects a boolean filter into the Approximate Nearest Neighbor (ANN) query.
# Metadata pre-filter query in a vector store
results = vector_index.query(
vector=query_embedding,
top_k=10,
filter={
"$and": [
{"tenant_id": {"$eq": "org_corp_9941"}},
{"allowed_groups": {"$in": ["engineering", "security_auditors"]}}
]
}
)- Strict Security Guarantees: Unauthorized vectors are excluded before distance calculations and ranking occur, preventing unauthorized data from influencing scoring or entering retrieval caches.
- The Selectivity Cliff in HNSW Graphs: Hierarchical Navigable Small World (HNSW) graphs construct proximity links across the entire vector space. When a metadata filter matches only a tiny fraction of the corpus (low selectivity, such as under 1% of chunks), standard greedy graph traversal hits dead ends, causing recall to collapse to zero unless the engine falls back to expensive brute-force scans.
- Tuple Explosion and Static Drift: If permissions are granted per user rather than per broad group, every vector payload must store large arrays of allowed identities. Updating permissions requires updating millions of vector payloads across the index.
2. Post-Filtering Authorization (Overfetch and Verify)
Post-filtering decouples vector indexing from access control. The vector database performs standard ANN search across the unpartitioned corpus, over-fetching a wide candidate set (for example, fetching 100 candidate chunks to return the top 10). The retrieval proxy then evaluates the candidate chunk IDs against an external authorization engine (such as OpenFGA or SpiceDB) and strips out inaccessible chunks.
- Index Integrity: Vector index graph connectivity remains optimal because ANN traversal is unconstrained by metadata masks.
- Top-K Starvation: If the most semantically relevant documents belong to restricted folders that the requesting user cannot access, all over-fetched candidates may be rejected by the authorization check, returning zero results even if accessible relevant documents exist lower in the corpus.
- Scoring Leakage: Over-fetching across forbidden documents incurs wasted compute and exposes the system to subtle timing side-channels regarding the existence and density of restricted knowledge domains.
3. Hybrid Two-Pass Retrieval
To mitigate the selectivity cliff and top-K starvation, production architectures deploy a two-pass hybrid approach:
- Coarse Pre-Filtering: Apply a high-level static filter at the vector database layer based on broad boundaries (such as tenant ID, organization unit, or classification level). This shrinks the candidate pool without creating graph fragmentation.
- ANN Vector Retrieval: Retrieve an over-fetched pool of candidates (typically 3x to 5x of the requested target count).
- Fine-Grained Dynamic ReBAC Post-Filtering: Execute a batched permission check against an external authorization service to filter the surviving candidates down to the final authorized set.
Relationship-Based Access Control (ReBAC) with Zanzibar Models
Enterprise document permissions are rarely flat lists of user IDs. Instead, they follow hierarchical graphs: a user inherits access to a document because they belong to an engineering team, which is assigned to a project folder, which contains a workspace document.
Baking these dynamic relationships into static vector metadata causes severe operational friction whenever an employee changes roles or a folder is shared with a new group. Modern enterprise RAG systems integrate Relationship-Based Access Control (ReBAC) inspired by the Google Zanzibar paper.
// OpenFGA Schema for Enterprise Document Chunks
model
schema 1.1
type user
type group
relations
define member: [user]
type folder
relations
define viewer: [user, group#member] or viewer from parent
define parent: [folder]
type document
relations
define parent_folder: [folder]
define direct_viewer: [user, group#member]
define viewer: direct_viewer or viewer from parent_folder
type chunk
relations
define parent_doc: [document]
define can_read: viewer from parent_docAuthorization Query Patterns: CheckPermission vs. LookupResources
When integrating Zanzibar engines like SpiceDB or OpenFGA into RAG pipelines, developers choose between two API operations:
- LookupResources (Pre-Filter Input): Resolves all document IDs a given user has permission to read. While this produces an exact list for vector pre-filtering, executing LookupResources across enterprise corpora with millions of objects introduces high latency (often over 150ms) and yields filter payloads too large for vector query engines.
- CheckBulkPermissions (Post-Filter Step): Evaluates a candidate list of 50 chunk IDs in a single RPC call against the user identity. Because evaluation runs concurrently over a bounded candidate list, p95 latency remains under 10ms.
import grpc
from authzed.api.v1 import (
Client,
CheckBulkPermissionsRequest,
CheckBulkPermissionsRequestItem,
ObjectReference,
SubjectReference
)
def filter_authorized_chunks(client: Client, user_id: str, candidate_chunks: list[dict]) -> list[dict]:
items = [
CheckBulkPermissionsRequestItem(
resource=ObjectReference(object_type="document_chunk", object_id=chunk["id"]),
permission="can_read",
subject=SubjectReference(object=ObjectReference(object_type="user", object_id=user_id))
)
for chunk in candidate_chunks
]
response = client.CheckBulkPermissions(CheckBulkPermissionsRequest(items=items))
authorized_ids = {
item.pair.resource.object_id
for item in response.pairs
if item.item.permissionship == 1 # PERMISSIONSHIP_HAS_PERMISSION
}
return [c for c in candidate_chunks if c["id"] in authorized_ids]Native Row-Level Security (RLS) in Relational Vector Stores
For architectures utilizing relational database extensions such as PostgreSQL with pgvector, access control can be pushed down to native PostgreSQL Row-Level Security.
In this architecture, application connections set session-level claims before issuing vector distance calculations:
-- Enable Row Level Security on the chunk embeddings table
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;
-- Define RLS policy joining dynamic user memberships
CREATE POLICY user_chunk_access_policy ON document_chunks
FOR SELECT
USING (
tenant_id = current_setting('app.current_tenant_id')::uuid
AND (
EXISTS (
SELECT 1 FROM document_permissions dp
WHERE dp.document_id = document_chunks.document_id
AND dp.subject_id = current_setting('app.current_user_id')::uuid
AND dp.permission_type = 'read'
)
OR EXISTS (
SELECT 1 FROM group_memberships gm
JOIN document_group_permissions dgp ON dgp.group_id = gm.group_id
WHERE gm.user_id = current_setting('app.current_user_id')::uuid
AND dgp.document_id = document_chunks.document_id
)
)
);Before executing the similarity search, the database session is scoped to the user context:
BEGIN;
SET LOCAL app.current_tenant_id = 'c7e8a931-1052-4a0b-8d5f-14930d6bf450';
SET LOCAL app.current_user_id = 'e2b3c109-8431-4122-bc08-5928f01b4431';
SELECT id, content, 1 - (embedding <=> $1) AS similarity
FROM document_chunks
ORDER BY embedding <=> $1
LIMIT 10;
COMMIT;RLS policies execute on candidate rows before returning them to the caller. With HNSW and IVFFlat indexes in PostgreSQL, complex subquery joins within RLS policies can prevent the query planner from effectively using the index, forcing index scans followed by iterative filtering that increases query latency compared to unconstrained vector queries.
Managing ACL Drift and Synchronization Lag
One of the most critical vulnerabilities in enterprise RAG systems is permission drift: a document's permissions are revoked in Google Drive or SharePoint, but the cached vector chunks remain accessible in the vector database.
To eliminate authorization drift without requiring full re-indexing of embeddings:
- Decouple Embedding Storage from Authorization State: Vector database payloads should only store the immutable object identifier (document ID, chunk ID) and high-level static boundaries (tenant ID). Never store flat lists of mutable user IDs in vector metadata.
- Change Data Capture (CDC) Event Pipelines: Connect enterprise source systems to Kafka or Debezium event buses. When a permission change occurs, publish the relationship delta directly to the ReBAC authorization service (SpiceDB or OpenFGA).
- Point-in-Time Token Checking: When an authorization check runs during retrieval, pass the source transaction timestamp (or Zanzibar zedtoken) to guarantee consistency between the authorization graph and the search request.
Architecture Trade-Offs
- Metadata Pre-Filtering: Delivers low latency (under 5ms pushdown) and strict exclusion guarantees, but suffers from severe recall collapse at low selectivity and requires index updates when ACL tags change.
- Post-Filtering (Pure): Preserves full vector graph connectivity and allows complex ReBAC evaluations, but incurs latency overhead and risks top-K starvation if top matches are restricted.
- Hybrid Two-Pass: Combines coarse tenant-level pre-filtering with fine-grained ReBAC post-filtering, balancing recall stability with 10-25ms p95 latency.
- Postgres Native RLS: Simplifies operational topology by embedding authorization logic directly into relational SQL transactions, with 15-40ms query execution overhead depending on index join complexity.
Implementation Guidelines
- Never delegate authorization to system prompts. Treat the LLM strictly as a text synthesis engine; only provide context chunks that have passed deterministic authorization checks.
- Use coarse-grained partition tags in vector metadata. Filter on tenant boundaries, environments, and immutable classification levels at the ANN search level to preserve index graph traversal performance.
- Enforce fine-grained permissions via centralized ReBAC. Use Zanzibar-based authorization engines to evaluate relationship graphs dynamically over the over-fetched retrieval set.
- Log every retrieved chunk alongside user context. Record the specific user identity, query text, chunk identifiers, and authorization evaluation tokens to ensure audit compliance across regulated environments.



