Build permission-aware RAG
Level: Pattern · Estimated time: 40 minutes
Outcome
Restrict retrieval with a filter derived from an authenticated principal. Relevance ranking happens only inside the corpus that principal is allowed to see.
When to use it
Use this for role- or tenant-scoped knowledge. For high-impact isolation, combine filters with separate namespaces, indexes, or database policy; do not rely on the model to hide forbidden text.
Request flow
authenticate → authorize knowledge:read → derive server-owned scope → vectorFilter → search → format permitted documents → answer. The question never supplies tenantId, role, or raw filters.
Setup and metadata
Store flat filterable metadata at embedding time:
const embedded = await embedDocuments(embeddingModel, documents, {
id: (document) => document.id,
content: (document) => document.text,
metadata: (document) => ({
tenantId: document.tenantId,
visibility: document.visibility,
status: document.status,
}),
});Derive the eligibility filter
import { vectorFilter } from "@anvia/core/vector-store";
function filterFor(principal: Principal) {
const visibility = principal.role === "manager"
? vectorFilter.or(
vectorFilter.eq("visibility", "staff"),
vectorFilter.eq("visibility", "manager"),
)
: vectorFilter.eq("visibility", "staff");
return vectorFilter.and(
vectorFilter.eq("tenantId", principal.tenantId),
vectorFilter.eq("status", "published"),
visibility,
);
}
const results = await index.search({ query, topK: 5, filter: filterFor(principal) });The same filter can be passed to AgentBuilder.dynamicContext(index, { filter, topK: 5 }).
Expected behavior
Two users asking the same question can receive different eligible results. A tenant-A principal must never retrieve a tenant-B canary even when that canary is the closest semantic match. An unauthenticated or unauthorized request returns before embeddings or completion are called.
Failure cases
Missing metadata should fail closed; a role downgrade must affect the next request; deleted or unpublished records must leave the eligible corpus; unsupported backend filters must be detected during adapter qualification, not assumed equivalent.
Security and ownership
Authentication and authorization are application responsibilities. Vector filters are an enforcement layer, not an identity system. Prefer physical isolation for strong regulatory or tenant boundaries, and independently authorize every tool called after retrieval.
Production changes and tests
Centralize filter construction, use opaque tenant identifiers, log decisions without document contents, and verify adapter semantics. Test cross-tenant canaries, each role pair, missing metadata, stale sessions, filter injection, unpublished content, and pagination/threshold behavior.
Runnable references
Extensions
Add per-document ACL expansion during ingestion, namespace routing, policy-decision caching with short TTLs, and an evaluation suite that treats any forbidden result as a hard failure.