Skip to content

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:

ts
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

ts
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.

Built for Anvia.