diff --git a/assets/spicedb-architecture.png b/assets/spicedb-architecture.png new file mode 100644 index 000000000..6771db771 Binary files /dev/null and b/assets/spicedb-architecture.png differ diff --git a/assets/spicedb-postfilter.png b/assets/spicedb-postfilter.png new file mode 100644 index 000000000..6771db771 Binary files /dev/null and b/assets/spicedb-postfilter.png differ diff --git a/assets/spicedb-prefilter.png b/assets/spicedb-prefilter.png new file mode 100644 index 000000000..2bc6b0d40 Binary files /dev/null and b/assets/spicedb-prefilter.png differ diff --git a/site/en/integrations/integrate_with_spicedb.md b/site/en/integrations/integrate_with_spicedb.md new file mode 100644 index 000000000..7bf714e96 --- /dev/null +++ b/site/en/integrations/integrate_with_spicedb.md @@ -0,0 +1,305 @@ +--- +id: integrate_with_spicedb.md +summary: Learn how to build a permission-aware RAG system using Milvus for vector search and SpiceDB for fine-grained, Zanzibar-inspired access control. +title: Permission-Aware RAG with Milvus and SpiceDB +--- + +# Permission-Aware RAG with Milvus and SpiceDB + +Most RAG systems treat retrieval as a relevance problem where a user types a query, gets the closest chunks back, and an LLM generates the answer from the data that is returned. However, this ignores a serious risk: **information leakage**. If different users have different levels of access to data, as they do in most real-world systems, your RAG pipeline must enforce those access boundaries. In fact, OWASP lists Sensitive Information Disclosure, Excessive Agency, and Vector and Embedding Weaknesses in their list of [Top 10 Risks for Large Language Model Applications](https://owasp.org/www-project-top-10-for-large-language-model-applications/). + +That's where fine-grained access control comes in. Instead of trusting the RAG system to "just do the right thing," you can integrate an authorization layer that determines which resources a given identity can access. This guide explores how to achieve that using [SpiceDB](https://authzed.com/spicedb), an open-source, Zanzibar-inspired permission system, together with Milvus. + +## Background + +### Google Zanzibar + +Google Zanzibar is the internal authorization system that manages permissions across all of Google's products and services. Think of it as the system that decides whether you can view a shared Google Doc, or edit the description of a YouTube video. Compared to traditional authorization methods such as Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC), Zanzibar utilizes Relationship-Based Access Control (ReBAC) where permissions are determined by relationships between users and resources. + +Zanzibar was built for requirements including: + +- Low-latency at Google-scale +- High-throughput authorization checks +- Global consistency of relationship data +- Hierarchical permission models (for example, files in folders, people in groups) + +RAG systems and Agentic AI typically share these requirements, which makes a Zanzibar-like system an ideal candidate for your permission layer. + +### SpiceDB + +SpiceDB is an open-source authorization system for fine-grained permission checks at scale, inspired by Google Zanzibar. It's used by companies such as OpenAI, Workday, and Turo. + +Together with Milvus, you can build a retrieval pipeline that respects your permission model. This guide covers two strategies for combining them: + +- **Pre-filter**: Ask SpiceDB which documents a user can access *before* the vector search, then restrict Milvus to that set. +- **Post-filter**: Retrieve candidates from Milvus by relevance first, then batch-verify permissions with SpiceDB. + +### How it works + +SpiceDB stores access relationships as a graph, where nodes represent entities (users, groups, documents) and edges represent relationships (such as "viewer," "editor," or "owner"). Authorization logic reduces to asking a single question: + +> Is this `actor` allowed to perform this `action` on this `resource`? + +In SpiceDB parlance, the actor and resource are both Objects, and the action is a Permission. + +Here's a Google Docs-style example where a user can be either a `reader` or a `writer` of a document. A reader can only `read` the document, whereas a writer can both `read` and `edit` it: + +``` +definition user {} + +definition document { + relation reader: user + relation writer: user + + permission edit = writer + permission read = reader + edit +} +``` + +This schema is intentionally minimal. In a real system, you'd model groups, organizational roles, folder-level inheritance, or resource hierarchies — all of which SpiceDB handles. The sample app later in this guide contains a more complex model. The retrieval integration pattern works regardless of schema complexity; only the relationship writes change. + +When a user requests access to a document, SpiceDB answers questions like: + +> "Can `user:alice` perform `read` on `document:doc1`?" + +It evaluates the relationship graph, enabling real-time authorization checks at massive scale. + +## How the demo is built + +This demo showcases an Agentic RAG system where a user asks a query and retrieves information based on the documents they're authorized to access. The "agentic" part refers to an AI agent that reasons about whether more data should be retrieved, but does **not** — and should not — reason about **whether** authorization is required. + +The system has three components: **Milvus** for document storage and retrieval, **SpiceDB** for authorization, and a **LangGraph** agent that orchestrates the flow. The demo also uses OpenAI for embeddings and LLM responses. + + + +### Who can see what + +The demo corpus spans four departments: engineering, sales, HR, and finance. There are also some public documents. Four users map to those departments: + +- `alice` → engineering +- `bob` → sales +- `hr_manager` → HR +- `finance_manager` → finance + +Department membership drives most access. An engineering document is visible to all engineering members by default. On top of that, there are three cross-department grants (for example, the engineering architecture document is also visible to Sales) and three individual exceptions. Public documents are granted to all four users explicitly. There is no `public` role in the schema — every permission is a concrete relationship in SpiceDB. + +### The agent graph + +The agent is a directed graph with four nodes: + +``` +retrieve → authorize → [reason] → generate +``` + +**Retrieval** embeds the user's query using OpenAI's `text-embedding-3-small` and searches the Milvus `Documents` collection for the top 5 semantically similar results. + +**Authorization** is the security boundary. It batch-checks every retrieved document against SpiceDB and splits them into authorized and denied sets. The graph structure enforces that this step is never skipped. + +**Reasoning** is optional. With `max_attempts=1` (the default), if all retrieved documents are denied, the agent goes straight to generation with an explanation. With `max_attempts > 1`, an LLM reasoning step kicks in: it can decide to retry with a modified query strategy. This is where the "agentic" part earns its name — the agent can recognize that its initial retrieval missed the mark and try again. + +**Generation** builds the final answer using only the authorized documents as context. The prompt is explicit: use only what's in the documents, quote directly, and if some results were denied, say so. + +## Milvus Setup + +Create a Milvus collection that includes a `doc_id` field. This field is the link between Milvus and SpiceDB — it maps each retrieved document to the resource ID that SpiceDB checks permissions against. + +The demo uses `text-embedding-3-small`, which produces 1536-dimensional vectors. The index is `IVF_FLAT` with `COSINE` metric, which is sufficient for a demo corpus. + +```python +from pymilvus import MilvusClient, DataType + +client = MilvusClient(uri="http://localhost:19530") + +if client.has_collection("Documents"): + client.drop_collection("Documents") + +schema = client.create_schema(auto_id=False, enable_dynamic_field=False) +schema.add_field("doc_id", DataType.VARCHAR, max_length=256, is_primary=True) +schema.add_field("title", DataType.VARCHAR, max_length=512) +schema.add_field("content", DataType.VARCHAR, max_length=65535) +schema.add_field("department", DataType.VARCHAR, max_length=128) +schema.add_field("classification", DataType.VARCHAR, max_length=128) +schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=1536) + +index_params = client.prepare_index_params() +index_params.add_index( + field_name="embedding", + metric_type="COSINE", + index_type="IVF_FLAT", + params={"nlist": 128}, +) + +client.create_collection("Documents", schema=schema, index_params=index_params) +``` + +
department and classification fields are not used by the authorization check, but they are useful metadata to return alongside content in the final response.
+