How ACLs and Metadata Filtering Work in Semantic Search

ACLs and metadata filtering work together in semantic search by deciding which documents are allowed before the search system decides which allowed documents are most relevant. This distinction matters because semantic similarity alone does not know who is allowed to see a document.

An access control list, or ACL, describes who can access an object. A metadata filter turns that access rule into something the retrieval system can enforce during search. In a secure semantic search or RAG system, the retriever should return only documents that are both relevant and permitted.

The wrong design is to run a broad vector search, retrieve private or unrelated documents, and remove unauthorized results afterward. The safer design is to build permission-aware filters first, then perform vector, keyword, or hybrid retrieval inside that eligible set.

What an ACL Means in Search

In normal application development, an ACL usually answers a simple question: who can read, edit, delete, or manage this object?

In semantic search, the same idea becomes more complex because the system is not fetching one known document by ID. It is searching across many possible documents and ranking them by meaning. The ACL must be enforced before the final result set is chosen.

A searchable document may have ACL fields like:

  • owner_user_id
  • tenant_id
  • allowed_user_ids
  • allowed_group_ids
  • allowed_roles
  • visibility
  • security_label
  • department, region, or workspace_id

These fields should not be treated as normal descriptive text. They are control fields. They decide whether an object is eligible for retrieval.

Why ACLs Are Harder With Vector Search

Traditional databases often retrieve a known object or run a structured query. Semantic search retrieves by meaning. That means the system may discover documents the user did not know existed.

This is powerful, but it creates risk. A user searching for “acquisition plans” might semantically match confidential board documents, legal memos, customer notes, and old drafts. The embedding model does not understand company permissions. It only understands similarity.

That is why ACLs must be converted into retrieval constraints. The vector database or retrieval layer needs a structured way to say: search for meaning, but only among objects this user is allowed to read.

The Basic Flow

A permission-aware semantic search flow usually looks like this:

1. Authenticate the user
2. Resolve the user's tenant, roles, groups, and permission scope
3. Convert that scope into metadata filters
4. Run vector, keyword, or hybrid search with those filters
5. Return only documents that match both relevance and access rules

The important point is that the permission filter comes from trusted backend context. It should not come from a raw user-controlled request.

Common ACL Models for Semantic Search

Different systems represent access in different ways. The best metadata design depends on how permissions work in the source application.

ACL modelHow it worksSearch metadata pattern
Owner-based accessThe owner can access the object.owner_user_id
Tenant accessAll content belongs to an organization or workspace.tenant_id or native tenant isolation
Role-based accessUsers inherit access from roles.allowed_roles
Group-based accessUsers inherit access from groups or teams.allowed_group_ids
Direct user grantsSpecific users are listed on the document.allowed_user_ids
Label-based accessUsers can read documents up to a clearance or label.security_label
Lifecycle accessDraft, archived, deleted, or published status affects retrieval.status

Many production systems combine several of these. For example, a document may be readable only when it belongs to the current tenant, is published, and either belongs to one of the user’s groups or is owned by the user.

Why ACL Metadata Should Be Denormalized

Search systems work best when the fields needed for filtering are stored directly with the searchable object or chunk. If every query needs to join across users, groups, folders, projects, and departments, retrieval becomes slower and harder to reason about.

For semantic search, it is usually better to denormalize permission metadata at ingestion time. That means each indexed object stores the access tokens needed for filtering.

{
  "title": "Q4 Account Strategy",
  "body": "...",
  "tenant_id": "org_123",
  "workspace_id": "sales",
  "allowed_group_ids": ["group_enterprise_sales", "group_customer_success"],
  "allowed_roles": ["manager", "account_owner"],
  "status": "published"
}

This does create a synchronization responsibility. When permissions change in the source system, the search index must be updated. But the benefit is that retrieval can enforce access quickly and consistently.

Pre-Filtering Is the Safe Default

ACL filters should be applied before final retrieval results are selected. This is called pre-filtering. The search system first determines eligible documents, then ranks eligible documents by vector similarity, keyword relevance, or hybrid score.

Post-filtering is weaker. If a vector search retrieves the top 10 global matches and then removes unauthorized results, the user may get too few results even though good authorized documents exist. Worse, unauthorized candidates influenced the retrieval process before being removed.

For ACLs, access is not a preference. It is a hard boundary. Hard boundaries should be part of candidate eligibility, not a cleanup step.

How ACL Filters Are Built at Query Time

At query time, the backend should build a permission expression from the authenticated user context. A simplified expression might look like this:

tenant_id = current_user.tenant_id
AND status = "published"
AND (
  owner_user_id = current_user.id
  OR allowed_user_ids contains current_user.id
  OR allowed_group_ids overlaps current_user.groups
  OR allowed_roles overlaps current_user.roles
)

This expression combines strict boundaries with flexible access grants. Tenant and status are mandatory. Ownership, direct grants, groups, and roles may be alternatives inside the same tenant boundary.

ACLs in RAG Systems

In RAG, ACL filtering must happen before context reaches the language model. The model should not receive unauthorized chunks, even temporarily.

A safe RAG flow looks like this:

User question
→ trusted identity context
→ ACL metadata filter
→ filtered semantic retrieval
→ optional reranking of allowed results
→ prompt built from allowed context
→ generated answer

If reranking is used, the reranker should also receive only allowed candidates. If caching is used, cache keys should include tenant and permission scope so one user’s retrieval results are not reused for another user incorrectly.

Avoid These ACL Mistakes

  • Do not let the frontend choose the tenant or permission scope directly.
  • Do not rely on post-filtering for sensitive content.
  • Do not embed access rules only in natural language text.
  • Do not forget lifecycle filters such as deleted, draft, archived, or expired.
  • Do not store permission fields without indexing them for filtering.
  • Do not assume vector similarity can replace authorization logic.

Implementation Example: Weaviate RBAC and Metadata Filters

Weaviate is a useful example because it supports both database-level authorization through RBAC and query-level metadata filters. These solve different layers of the problem.

RBAC controls what a user or service account can do at the database resource level, such as reading data from a collection or tenant. Metadata filters control which objects inside an allowed search scope are eligible for a specific query.

For tenant-level security, a role can be scoped to a tenant:

from weaviate.classes.rbac import Permissions

client.roles.create(
    role_name="HospitalA_Clinician",
    permissions=[
        Permissions.data(
            collection="PatientRecords",
            tenant="hospital_a",
            read=True,
        ),
    ],
)

client.groups.oidc.assign_roles(
    group_id="HospitalA-Clinicians",
    role_names=["HospitalA_Clinician"],
)

That role boundary helps prevent a user from reading another tenant’s data at the database authorization layer.

Inside an allowed tenant, metadata filters can enforce document-level rules during semantic search:

from weaviate.classes.query import Filter, MetadataQuery

base_collection = client.collections.use("PatientRecords")
tenant_collection = base_collection.with_tenant("hospital_a")

filters = (
    Filter.by_property("status").equal("active") &
    Filter.by_property("department").equal("cardiology") &
    (
        Filter.by_property("allowed_roles").contains_any(["clinician"]) |
        Filter.by_property("allowed_group_ids").contains_any(["cardiology_team"])
    )
)

response = tenant_collection.query.near_text(
    query="recent discharge instructions for heart failure",
    limit=10,
    return_metadata=MetadataQuery(distance=True),
    filters=filters,
)

for obj in response.objects:
    print(obj.properties)
    print(obj.metadata.distance)

This example uses tenant-aware querying for the broad boundary and metadata filters for document-level access. The same pattern can apply to customer support knowledge bases, legal document search, internal enterprise search, and SaaS workspaces.

RBAC Is Not a Replacement for Document ACLs

Database RBAC and document ACL filtering should not be confused. RBAC decides what a user or service can do with database resources. Document ACL filters decide which records are eligible inside a search query.

For example, a backend service account may have permission to read a collection so it can serve many users. That does not mean every end user should see every document the service account can technically read. The application still needs to apply end-user ACL filters for each request.

Best Practices

  1. Use database authorization for coarse resource boundaries.
  2. Use metadata filters for document-level ACL enforcement.
  3. Derive filters from trusted authenticated identity, not from user input.
  4. Apply ACL filters before vector or hybrid result selection.
  5. Denormalize searchable permission tokens onto indexed objects or chunks.
  6. Update indexed ACL metadata when source permissions change.
  7. Include tenant, status, and lifecycle filters in every protected query.
  8. Audit denied access attempts and unusual cross-scope query patterns.

Summary

ACLs and metadata filtering make semantic search permission-aware. ACLs describe who can access a document, while metadata filters turn those rules into query-time constraints that the retrieval system can enforce.

For secure semantic search and RAG, ACL filters should be applied before final results are selected. Tenant scope, roles, groups, ownership, visibility, status, and security labels should narrow the eligible search space before vector similarity or hybrid ranking decides what is most relevant.

The strongest design uses multiple layers: database authorization for broad resource access, native tenant isolation where available, and metadata filters for document-level permissions inside each search request.