Choose security filters or built-in ACL trimming¶
Draft: not validated
Not yet dry-run. Do not use on a live system until the validation record is complete and signed off. Drafted with AI from official sources on 16 October 2026, verified against official documentation on 29 October 2026. Author: Yogeshwaran · dry-run: Ranjith · sign-off: James. Work items: RAG-04-T01, RAG-04-T02, RAG-04-T03, RAG-04-T04. Planned sign-off: 2026-11-30.
1. Summary¶
This decision guide establishes how to enforce document-level access control in Retrieval-Augmented Generation (RAG) solutions so users never receive information from documents they cannot read in the source system. It evaluates three architectures: Option A (application-enforced security filters on index-time permission fields), Option B (built-in query-time ACL trimming via x-ms-query-source-authorization), and Option C (post-retrieval verification). Option A is the recommended default for all production workloads on Azure AI Search. Option B is in preview (2026-08-01-preview) and has no SLA. Facts checked against Microsoft Learn and AWS documentation on 29 October 2026.
2. When to use and when not to¶
Use this guide when:
- Designing or reviewing access control for any RAG solution over permissioned enterprise data (SharePoint Online, ADLS Gen2, Azure Blob Storage, on-premises file shares, Confluence).
- Evaluating whether to use native Azure AI Search connectors or custom ingestion pipelines with security metadata fields.
- Establishing the accepted revocation delay with business and security stakeholders (the time between a permission change in the source and its enforcement in search).
- Benchmarking the query latency and storage overhead of permission metadata against search partition quotas.
Do not use it when:
- Implementing the chosen security filter logic and Microsoft Graph group resolution in code. Use PB-RAG-04-02 instead.
- Handling an active permission leak incident or urgent document revocation. Use PB-RAG-04-03 and PB-RAG-04-04 instead.
- Rebuilding the search index or switching index aliases during schema updates. Use PB-RAG-09-01 instead.
- All documents in the corpus are public or identically accessible to all users without authorization boundaries.
3. What you must know first¶
- Security filter mechanics. Azure AI Search evaluates OData
$filterexpressions on indexed fields before ranking and scoring documents. An application injects the caller's user and group identifiers into an equality orsearch.inexpression (for example:group_ids/any(g: search.in(g, '${GROUPS}', ',')) or user_ids/any(u: u eq '${USER_ID}')). Backed by Security filters for trimming results in Azure AI Search. - Built-in ACL trimming is preview. Native query-time access control using the
x-ms-query-source-authorizationheader is in preview (API version2026-08-01-preview). Microsoft preview features do not provide an SLA, can change between API versions without notice, and should not be deployed in production without written client sign-off. Backed by Document-level access control in Azure AI Search. - Chunk-level permission projection. In chunked vector indexes, search returns chunks rather than parent files. Every chunk document must store a projection of the parent document's permission fields (
user_ids,group_ids), or access control cannot be evaluated during retrieval. Backed by Security filters for trimming results in Azure AI Search. - Revocation delay is unavoidable in search indexes. When an administrator revokes access in a source repository, the index retains the old permissions until the next indexer sync, push update, or explicit document patch. Teams must establish and document an accepted revocation delay with stakeholders. Backed by Update or rebuild an index in Azure AI Search.
- Transitive group resolution and token overage. Users often receive access through nested Entra ID security groups. Microsoft Entra ID access tokens omit group claims when a user belongs to more than 200 groups (overage condition), emitting an overage claim instead. The application must resolve nested memberships via Microsoft Graph
/transitiveMemberOf. Backed by Configure group claims and app roles in tokens and List a user's memberships, direct and transitive. - Query clause and payload limits. Azure AI Search enforces query complexity limits (up to 3,000 clauses per query) and HTTP GET request length limits (8 KB). Applications with users belonging to hundreds of groups must use
search.inover HTTP POST (up to 16 MB request limit) rather than chainedorclauses. Backed by OData search.in function reference and Service limits for Azure AI Search.
4. Options¶
Decision tree¶
Use this decision tree to select the appropriate access control pattern:
graph TD
Start["New permissioned RAG solution"] --> Q1{"Production system requiring an SLA?"}
Q1 -- Yes --> Q2{"Are source permissions volatile (<5 min revocation SLA)?"}
Q1 -- No --> Q3{"Sourced strictly from SharePoint/ADLS with Entra ID?"}
Q2 -- Yes --> OptC["Option C: Post-retrieval verification<br/>(dual-layer trimming)"]
Q2 -- No --> OptA["Option A: Security filters<br/>(index-time ACL projection - DEFAULT)"]
Q3 -- Yes --> OptB["Option B: Built-in ACL trimming<br/>(x-ms-query-source-authorization preview)"]
Q3 -- No --> OptA
The 7-stage evaluation process¶
Follow this operational workflow to evaluate, prototype, and sign off the access control architecture:
- Catalog repositories and permission models. Inventory every data source (SharePoint, ADLS Gen2, file shares) and identify whether permissions are managed via Entra ID groups, user principal names, or POSIX ACLs. Verify: Every data source has its permission source and identity mapping documented.
- Establish the accepted revocation delay SLA. Agree with security and business stakeholders on the maximum tolerable delay between an access revocation in the source system and its enforcement in RAG retrieval.
Verify: A formal revocation SLA is recorded in
snippets/trimming-decision-record.md. - Define index security schema and chunk projection. Include
group_ids(Collection(Edm.String)),user_ids(Collection(Edm.String)), andparent_id(Edm.String) on the index schema. Ensure ingestion pipelines project parent permissions onto all child chunks. Verify: Target index schema matches snippets/security-fields.json withfilterable: trueandretrievable: false. - Test directory resolution and token overage handling. Verify that the application retrieval layer authenticates callers, inspects token group claims, and falls back to Microsoft Graph
/transitiveMemberOfif the user has more than 200 groups (overage condition). Verify: A test call with an overage persona resolves all transitive security group GUIDs within 200ms. - Execute two-persona sandbox proof of concept. Deploy a sandbox index seeded with a restricted document and an open document. Query the index with Persona 1 (authorized) and Persona 2 (unauthorized) across keyword, vector, and hybrid search. Verify: Persona 1 retrieves the restricted document; Persona 2 receives 0 matching documents across all query modes.
- Audit every retrieval path across the application. Confirm that all search endpoints (text search, semantic search, vector search, autocomplete, suggestions, agent tool retrieval) invoke the shared security filter retrieval wrapper. Verify: Direct calls to Azure AI Search from frontend clients or autonomous tools without filters are blocked by architectural policy.
- Complete the decision record and obtain security sign-off. Document the selected option, benchmark metrics, and revocation SLA in the decision record. Verify: Dated sign-offs from the Solution Architect and Security Lead are recorded in snippets/trimming-decision-record.md.
Critical security risk: unmonitored query paths leak documents
Omitting security filters on secondary query endpoints (such as autocomplete, document lookup, or an autonomous agent tool) exposes restricted document content or titles to unauthorized callers. All retrieval operations must be forced through a single shared data-access function. Test every query interface before deployment.
Rollback procedures¶
- Option A (Security filters): If a filter syntax error or regression causes query failures, roll back the application service deployment to the previous stable release. If a data leak is discovered, immediately inject an emergency quarantine filter (
(group_ids/any(...) and parent_id ne '${LEAKED_DOC_ID}')) in the shared retrieval function, or run PB-RAG-04-03 to purge the affected chunks. - Option B (Built-in ACL trimming, preview): If the preview API behaves inconsistently or experiences breaking upstream changes, roll back by removing the
x-ms-query-source-authorizationheader, updating the query layer to Option A security filters, or repointing the index alias to a stable index (PB-RAG-09-01). - Option C (Post-retrieval verification): If the upstream permission API (such as Microsoft Graph) suffers an outage or returns HTTP 429 throttling, configure the retrieval middleware to fail closed (returning 0 documents with an authorization error message) rather than bypassing checks.
Option A: Application-enforced security filters (index-time ACL projection)¶
During ingestion, the pipeline extracts document access control lists (user object IDs and security group IDs) and maps them to multi-value string collections on the index (group_ids, user_ids). When documents are chunked, the parent document's permissions are projected onto every child chunk. At query time, the application layer authenticates the user, resolves their direct and transitive group memberships from Microsoft Entra ID, and injects an OData $filter into every search request.
- When it fits: The required standard for all production enterprise RAG systems. Fits any data source (SharePoint, ADLS Gen2, Amazon S3, internal file shares, databases) and any identity provider (Microsoft Entra ID, Okta, Ping, Google Workspace).
- Cost and risk: Modest increase in index storage (typically 5-15%) for the security metadata fields across chunks. Risk: A software bug that omits the filter on an obscure query path (such as autocomplete or a secondary agent tool) will leak data. Enforce retrieval through a single shared data-access function.
- Carried out by: PB-RAG-04-02.
Option B: Built-in query-time ACL trimming (preview)¶
Azure AI Search native indexers ingest file permissions directly into internal access structures. At query time, the client application passes the calling user's OAuth access token in the x-ms-query-source-authorization header (2026-08-01-preview). The search service natively evaluates document access control lists against Entra ID directory groups at query time without application-managed OData filters.
- When it fits: Experimental prototypes, sandbox demonstrations, or internal non-production applications sourced strictly from SharePoint Online or ADLS Gen2 where the client explicitly signs off on preview terms.
- Cost and risk: Eliminates custom ACL ingestion pipeline code. Risk: preview feature with no SLA; breaking API changes may occur without notice; limited to supported Microsoft 365 and Azure data sources; higher query latency due to query-time identity validation.
- Carried out by: Native indexer configuration and preview SDKs.
Option C: Post-retrieval verification (dual-layer trimming)¶
Search runs under an elevated application service principal without fine-grained security filters (or with coarse organization-level filters). The application intercepts the retrieved chunks and queries the source system's API (such as Microsoft Graph or SharePoint REST) in real time to confirm whether the calling user has active read permissions before including the chunk in the LLM prompt.
- When it fits: Niche scenarios with highly volatile permissions where even 5 minutes of revocation delay is unacceptable, or where complex permission models (dynamic conditional access, sensitivity labels) cannot be mapped to index fields.
- Cost and risk: Heavy latency penalty (100-500ms added per query); severe risk of upstream API rate-limiting (e.g. Microsoft Graph 429 throttling); potential recall collapse if the top retrieved chunks are pruned post-retrieval, leaving the LLM with no grounding context.
- Carried out by: Custom retrieval orchestrator middleware.
Option comparison¶
| Dimension | Option A: Security filters | Option B: Built-in ACL trimming (preview) | Option C: Post-retrieval verification |
|---|---|---|---|
| Production readiness | GA (standard SLA) | Preview (no SLA) | Custom architecture |
| API stability | Stable (2026-04-01) |
Preview (2026-08-01-preview) |
Stable API dependencies |
| Identity providers | Any (Entra ID, Okta, Ping) | Microsoft Entra ID only | Any with upstream check API |
| Supported data sources | Any repository | SharePoint, ADLS Gen2 | Any with item permission check |
| Revocation delay | Index sync cycle (or PB-RAG-04-03) | Next indexer run | Immediate (real time) |
| Query latency impact | Low (<5ms) | Medium (10-30ms) | High (100-500ms+) |
| Throttling risk | None on search service | Low | High (upstream API 429s) |
| Rollback capability | Instant (app rollback / alias switch) | Complex (rebuild index / code change) | Instant (app configuration flag) |
| Carried out by | PB-RAG-04-02 | Native indexer config | Custom orchestrator |
Index schema and query filter¶
Index schema definition for security trimming fields (snippets/security-fields.json):
[
{ "name": "chunk_id", "type": "Edm.String", "key": true, "filterable": true },
{ "name": "parent_id", "type": "Edm.String", "filterable": true },
{ "name": "content", "type": "Edm.String", "searchable": true },
{ "name": "group_ids", "type": "Collection(Edm.String)", "filterable": true, "retrievable": false },
{ "name": "user_ids", "type": "Collection(Edm.String)", "filterable": true, "retrievable": false },
{ "name": "acl_synced_at", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true }
]
Example query filter generated by the application layer:
# Query filter injected by shared retrieval function:
filter="group_ids/any(g: search.in(g, '${GROUP_IDS}', ',')) or user_ids/any(u: u eq '${USER_ID}')"
Supporting snippets:
- snippets/security-fields.json: Full JSON index schema defining security fields.
- snippets/trimming-decision-record.md: Architecture Decision Record template with agreed revocation delay.
5. Decision table¶
| Situation | Choose | Why |
|---|---|---|
| Production enterprise RAG on Azure AI Search | Option A | Generally available with standard SLA; sub-5ms filter evaluation latency. |
| Non-Microsoft identity provider (Okta, Ping, Google) | Option A | Built-in trimming requires Microsoft Entra ID tokens exclusively. |
| Non-Azure data sources (AWS S3, local SMB shares, Confluence) | Option A | Native indexer ACL ingestion only supports select Azure and M365 connectors. |
| Greenfield SharePoint Online POC with written preview sign-off | Option B | Fast setup using native indexer without developing a custom ACL ingestion pipeline. |
| Highly volatile permissions where even 5 minutes of revocation lag is unacceptable | Option C | Validates permissions against the authoritative source at query time. Pair with PB-RAG-04-03 for emergency purges. |
| High-throughput conversational assistant (>50 QPS) | Option A | Avoids per-query external token authorization calls that introduce latency and rate-limiting risks. |
| User belongs to >200 Entra ID groups | Option A | Handles Entra ID token overage by resolving transitive groups via Microsoft Graph and caching results. |
| Compliance mandates immediate access cutoff upon employee departure | Option A + PB-RAG-04-03 | Use security filters for general retrieval and invoke chunk ACL patching runbook during offboarding events. |
6. Recommended default and when to deviate¶
Default: Use Option A (Application-enforced security filters) for all production systems. It is generally available, supports any enterprise repository, isolates search queries from upstream identity directory outages, and maintains consistent sub-second response times.
When to deviate:
- Deviate to Option B only for non-production proofs of concept or sandbox trials sourcing strictly from SharePoint Online or ADLS Gen2, provided the client signs off on preview terms in writing.
- Deviate to Option C only when permissions change every few seconds and business policy strictly forbids even momentary revocation lag, provided query volumes are low enough to avoid upstream API rate limits.
Rule of thumb: If you cannot accept an unannounced API change breaking your production retrieval pipeline or leaking data, choose Option A.
7. Known issues and fixes¶
| Symptom | Likely cause | Fix |
|---|---|---|
| Nested group members cannot see documents they are entitled to view | Entra ID token group claims omit indirect memberships, or user exceeded the 200-group limit (overage claim emitted) | Resolve transitive memberships using Microsoft Graph /transitiveMemberOf at query time and cache with a short TTL (e.g. 5-15 minutes). |
Search returns HTTP 400 ClauseLimitExceeded or request URI too long |
Filter expression chains multiple or clauses (group_ids/any(g: g eq 'id1' or g eq 'id2'...)) over HTTP GET |
Switch to search.in(g, '${GROUPS}', ',') and execute queries via HTTP POST (payload limit 16 MB). |
| Revoked user continues to retrieve restricted documents after permission removal | Source permission changes have not reached the search index; scheduled indexer has not run | Execute PB-RAG-04-03 to patch chunk ACLs immediately via mergeOrUpload. |
| Query returns zero results when security filter is combined with search text or business filters | OData operator precedence error binds and tighter than or (search=text&$filter=public eq true or group_ids/any(...)) |
Enclose the security filter in parentheses: ((group_ids/any(...) or user_ids/any(...)) and (${BUSINESS_FILTER})). |
| Suggest or Autocomplete endpoint leaks confidential document titles | Autocomplete query path was implemented without injecting security trimming filters | Wrap all search, suggest, autocomplete, and lookup calls inside a single shared retrieval service that forces security trimming. |
| Preview API changes schema or behaviour unexpectedly | Azure AI Search updated preview endpoint behavior | Pin the API version in client configurations (2026-08-01-preview) and validate upgrades in a sandbox. |
8. Monitoring and alerts¶
The alert thresholds below are illustrative team defaults, not platform recommendations. Tune them against a measured baseline.
| Signal | Source | Alert when | Action |
|---|---|---|---|
| Permission leakage test suite in CI | CI pipeline / automated test run | Any forbidden document returned for a test persona | Block release deployment and start PB-RAG-04-04. |
SearchLatency |
Platform metric (average) | Above 1.5x baseline for 10 minutes | Check filter clause count and group array sizes; verify query-time group cache hit rate. |
| Microsoft Graph 429 throttling | Application telemetry | Above 0.5% of group resolution requests throttled | Increase group cache TTL or implement Redis distributed token caching. |
| Indexer permission extraction errors | AzureDiagnostics (status = 'transientFailure') |
itemsFailed > 0 on permission fields during crawl |
Inspect crawler credentials and source permissions; pause indexer to prevent indexing documents with null ACLs. |
9. Cost and capacity notes¶
- Index storage expansion: Projecting
group_idsanduser_idsonto every chunk increases vector and document storage by 5-15% depending on ACL granularity. Check partition storage limits before scaling. Check current service quotas in Service limits for Azure AI Search. - Query compute overhead:
search.inevaluation adds minimal CPU overhead (typically 1-5ms per query), well within standard query quotas. - Directory API costs: Microsoft Graph calls for transitive group resolution are subject to Graph request quotas. Query-time caching is mandatory to avoid throttling.
10. AWS and open-source equivalents¶
| Platform | Equivalent | Key differences |
|---|---|---|
| Amazon Bedrock Knowledge Bases | Metadata filters (retrievalConfiguration) |
Bedrock supports metadata filters (equals, in, and, or) on retrieved chunks. There is no native Entra ID integration; the ingestion pipeline must extract permissions and write them into S3 metadata JSON files. |
| OpenSearch / Elasticsearch | Document-Level Security (DLS) or Terms filter | OpenSearch Security plugin provides native DLS using cluster roles. For external identity stores, applications use a terms query filter on a keyword field (allowed_groups) matching the caller's tokens. |
| PostgreSQL + pgvector | Row-Level Security (RLS) | Enforced directly inside PostgreSQL via SQL policies on session parameters (e.g. current_setting('app.current_groups')). Instant revocation upon session reset, but less efficient than inverted index keyword collections when documents have hundreds of groups. |
11. Validation record¶
Pending. Filled in by the dry-runner (see docs/validation-protocol.md).
Sandbox scenario: Deploy a test Azure AI Search index seeded with 10 documents tagged with distinct group IDs (group-finance, group-engineering, group-all). Execute keyword search, vector search, and hybrid search across three test personas (Finance user, Engineering user, Unprivileged user). Compare Option A (injected search.in filter) and Option B (simulated header).
Pass criteria: In all search modes, the unprivileged user receives 0 restricted documents; the Finance user receives only Finance and All documents; filter evaluation adds less than 15ms to query latency; and the decision record template is fully completed.
12. Sources¶
Facts checked against official documentation on 29 October 2026.
- Microsoft Learn, Security filters for trimming results in Azure AI Search: Security filter pattern using OData
$filterexpressions on group fields. - Microsoft Learn, Document-level access control in Azure AI Search: Overview of native document-level access control and preview status.
- Microsoft Learn, Query-time ACL and RBAC enforcement in Azure AI Search: Query-time enforcement using
x-ms-query-source-authorization. - Microsoft Learn, Index content from SharePoint in Microsoft 365 (preview): SharePoint indexer capabilities and preview caveats.
- Microsoft Learn, OData search.in function reference: Syntax, delimiter handling, and optimization over chained OR expressions.
- Microsoft Learn, Service limits for Azure AI Search: Query clause limits (3,000 clauses) and request size limits (8 KB GET, 16 MB POST).
- Microsoft Learn, Azure AI Search monitoring data reference: Platform metrics including
SearchLatencyand resource logs. - Microsoft Learn, List a user's memberships, direct and transitive (Microsoft Graph): Resolving nested Entra ID group memberships.
- Microsoft Learn, Configure group claims and app roles in tokens: Group claim emission and 200-group overage indicator.
- AWS Documentation, Query a knowledge base and retrieve data (Amazon Bedrock): Metadata filtering on Amazon Bedrock Knowledge Bases retrieval.
Related playbooks¶
- PB-RAG-04-02: Implement security-filter trimming with Entra ID groups.
- PB-RAG-04-03: Apply an urgent permission change or revocation.
- PB-RAG-04-04: Permission leakage test and incident checklist.
- PB-RAG-05-01: Ingestion pipeline design.
- PB-RAG-09-01: Reindex an Azure AI Search index without downtime.