Access Control & Authorization
Production Large Language Model (LLM) applications require robust security perimeters. Unlike traditional applications where users query static database rows, AI systems execute complex workflows (retrieval-augmented generation, tool calling, database writes) on behalf of users.
Enforcing safety boundaries requires shifting from simple front-end validation to a backend Policy-Driven Authorization model.
๐ 1. Secure AI Authorization Flow
A secure LLM application wraps the model generation loop within input policies, vector store filters, and output sanitizers.
๐๏ธ 2. Core Access Control Patterns
AI systems implement three key authorization patterns depending on scale and metadata complexity:
Role-Based Access Control (RBAC)
Simplest pattern. Users are assigned static roles (e.g., guest, employee, admin), which map directly to permitted actions.
- Best For: Restricting access to specific models (e.g., only
prosubscribers can query GPT-4o, whilefreeusers are routed to GPT-4o-mini).
Attribute-Based Access Control (ABAC)
Evaluates dynamic attributes at runtime (e.g., client IP, country, active session time, or document confidentiality level).
- Best For: Preventing data egress during out-of-office hours or blocking cross-border data transfer compliance violations.
Relationship-Based Access Control (ReBAC)
Enforces access based on relationships between entities (e.g., โUser A is a member of Team B, which owns Document Cโ).
- Best For: Granular knowledge base permissions where users can only query documents uploaded by their workspace team.
๐ 3. Secure RAG Ingestion (Row-Level Security)
In a RAG system, developers often make the mistake of performing a vector search across the entire database, and then filtering the retrieved results post-generation. This introduces information leakage and degrades performance.
Instead, always enforce Row-Level Security (RLS) by injecting authorization filters directly into the database query segment (Pre-Query Filtering).
Secure pgvector Ingestion with Row-Level Security
The following SQL schema illustrates how to configure Row-Level Security in PostgreSQL to ensure users can only search vector embeddings they are authorized to access:
-- Create tenant organization table
CREATE TABLE organizations (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Create a table for documents with owner organization relations
CREATE TABLE document_store (
id UUID PRIMARY KEY,
org_id UUID REFERENCES organizations(id) NOT NULL,
content TEXT NOT NULL,
embedding vector(1536) NOT NULL
);
-- Enable Row-Level Security on the document store
ALTER TABLE document_store ENABLE ROW LEVEL SECURITY;
-- Create an RLS policy matching tenant org_id to the session user context config
CREATE POLICY tenant_document_access_policy ON document_store
FOR SELECT
USING (org_id = NULLIF(current_setting('app.current_org_id', true), '')::uuid);Implementing Secure Query Execution (Python)
When querying the database, the backend application must set the active session context inside a transaction block before invoking the vector search:
import psycopg2
from typing import List, Dict
def secure_vector_search(conn, org_id: str, query_embedding: List[float], limit: int = 5) -> List[Dict]:
with conn.cursor() as cur:
# 1. Start transaction block
cur.execute("BEGIN;")
# 2. Inject session-level tenant identifier context
cur.execute("SET LOCAL app.current_org_id = %s;", (org_id,))
# 3. Execute vector cosine similarity search
# RLS automatically restricts traversal to rows matching the org_id
cur.execute("""
SELECT id, content, (embedding <=> %s::vector) as distance
FROM document_store
ORDER BY embedding <=> %s::vector
LIMIT %s;
""", (query_embedding, query_embedding, limit))
results = [{"id": row[0], "content": row[1], "distance": row[2]} for row in cur.fetchall()]
# 4. Commit transaction, clearing local session context
cur.execute("COMMIT;")
return results๐ 4. Fine-Grained Authorization with Policy Engines
For complex microservices, decouple authorization logic from application code by utilizing centralized Policy-as-Code engines (such as Open Policy Agent or Permify).
Below is an authorization schema configured for a multi-tenant corporate handbook knowledge base:
// Policy definition representing access constraints
entity user {}
entity workspace {
relation owner @user
relation employee @user
}
entity knowledge_document {
relation parent_workspace @workspace
attribute classification_clearance integer
// Admins / Workspace owners can read any clearance level
permission admin_view = parent_workspace.owner
// Employees can view documents if their clearance matches the document's classification
permission employee_view = parent_workspace.employee and check_clearance(classification_clearance)
action view = admin_view or employee_view
}
rule check_clearance(classification_clearance integer) {
// Only return documents with classification level <= 2 (Public/Internal) for employees
classification_clearance <= 2
}๐ผ 5. Best Practices
- Least-Privilege API Keys: When configuring external tools, never use admin database keys or wildcard service accounts. Use scoped, read-only API keys for vector search.
- Enforce Output Redaction: Implement a post-generation regex/PII classifier loop to block LLM responses containing credit cards, passwords, or emails, preventing accidental data exposure.
- Auditable Telemetry: Log all authorization decisions (
ALLOWorDENY) and tie them to tracing IDs (trace_id) in your observability stack to detect adversarial scanning attempts.
๐ Related Sections
- Observability & Tracing โ Logging security alerts, token consumption anomalies, and tracing injection attempts.
- Agent Security & Guardrails โ Scoped tool roles, network isolation, and sandboxing code execution runtimes.
- Anatomy of RAG Systems โ Multi-stage retrieval architectures and vector index configurations.