The situation
A knowledge team wants an internal assistant that answers staff questions from company documents. The HR handbook and policies sit in S3. Engineering runbooks are in Confluence, deal notes in Salesforce, and a few hundred PDFs on a shared drive. Generation is settled: a Claude model on Amazon Bedrock writes the answers. Retrieval is not.
Two constraints shape the build. The corpus spans four repositories with four permission models, and the assistant must never surface a passage an employee is not cleared to read, so an HR investigation note cannot turn up inside an engineer’s answer. The team is also small, with no appetite for running and tuning a vector database by hand.
A year ago this had a ready-made answer. Amazon Kendra crawled document ACLs alongside document content, and dropping it in as the retriever was the path of least resistance. AWS moved Kendra into maintenance mode on 30 June 2026 and closed it to new customers on 30 July. Existing indexes keep running and stay supported. This team has none.
What actually matters
Enforcement has to happen inside retrieval. Once a forbidden passage reaches the model’s context, prompting does not reliably keep it out of the answer, and a filter over the generated text is guesswork applied after the leak. Bedrock has two mechanisms that sit inside retrieval, and they are not interchangeable.
The first is ACL awareness on a managed knowledge base. Turn it on per data source, and ingestion crawls each document’s allowed and denied users and groups alongside its content. A Retrieve call then carries a user context holding the caller’s email, and results come back filtered to what that person may read. SharePoint, OneDrive, Google Drive and both Confluence editions add a real-time check against the source for every document returned, which catches permission changes made since the last sync. S3 has no permission system to crawl, so its ACLs come from a file the team writes. The web crawler supports none of it, since web pages carry no permission model.
Three properties of that mechanism decide whether it is safe here. AWS documents it as ACL-aware filtering rather than authorization: Bedrock authenticates nobody, so the email in the user context is trusted exactly as far as the application that supplied it. The email is the only identifier the application passes, and it is matched exactly, with no alias resolution across identity providers, so an address that differs between Confluence and the directory yields no results and no error. Group membership is not passed; Bedrock resolves it from what the connector crawled. And it fails closed everywhere, including in the cases that surprise people. Deny beats allow. A document with no ACL in an ACL-enabled S3 source is never ingested at all. A Retrieve without a user context returns zero results from ACL-enabled sources, while any source in the same knowledge base that has ACL awareness switched off still returns everything to everyone.
Connector coverage then decides the shape of the build, and connecting a repository is not the same as filtering it. Managed knowledge bases connect to S3, Confluence Cloud and Data Center, SharePoint, OneDrive, Google Drive, Box, ServiceNow, Salesforce, Zendesk, the web crawler, and a custom source. ACL awareness runs on a shorter list: S3, both Confluence editions, SharePoint, OneDrive, Google Drive, Box, ServiceNow and the custom source. Salesforce and Zendesk connect without it, and AWS says so flatly: every authenticated caller who can query the knowledge base sees everything crawled from them. Put the deal notes beside the HR handbook and the handbook’s filtering does not reach them. The other kind of knowledge base, the one where you bring your own vector store, has no ACL crawling at all, and enforcement there is metadata filtering, built on attributes assigned at ingestion and a filter expression the application composes from the caller’s identity. Three of this team’s four repositories land on the first path. The deal notes land on the second, and they get there through S3 or custom ingestion rather than through a Salesforce connector.
What we’ll filter on
- Enforcement inside retrieval, rather than a prompt instruction or a filter over the generated answer.
- Permissions crawled from the source, against a mapping from identity to filters that the team writes and maintains.
- Per-user filtering on all four repositories, not just a connector that reaches them.
- Control over ChunkingSplitting documents into retrievable pieces before embedding them – small enough to match precisely, big enough to still make sense., the embedding model, and the vector store.
- What comes back: ranked chunks the application composes into an answer, or a finished answer with citations.
- Availability to a team with no index already running.
The landscape
Managed knowledge base with ACL awareness. The build AWS now recommends, and the closest thing left to Kendra’s old shape. Bedrock runs ingestion, holds the vector store, and answers Retrieve or AgenticRetrieveStream; agentic retrieval generates a response by default, so a synthesised answer with citations does come back in one call. Tuning is limited by design. Chunking is default, fixed-size or none, with the default splitting at 300 tokens and 20% overlap, and neither semantic nor hierarchical chunking is offered. Retrieval is always hybrid, keyword plus semantic, with no semantic-only mode. The embedding model is the one choice left open: a service-managed model at no extra cost, or any Bedrock embedding model that produces float32 vectors at 1024 dimensions. The vector store is not a choice at all. Data sources can be synced daily, weekly, monthly or on demand.
Vector knowledge base with metadata filtering. The knowledge base you assemble, which now takes S3 and custom ingestion; from 30 September 2026 AWS stopped supporting new Confluence, SharePoint, Salesforce and web crawler connectors on this kind, so anything else arrives as objects you land yourself. You pick the embedding model, the chunking strategy (fixed-size, semantic, hierarchical or none), and the vector store: OpenSearch Serverless, an OpenSearch managed cluster, S3 Vectors, Aurora PostgreSQL, Neptune Analytics, or a third-party store such as Pinecone or MongoDB Atlas. Both Retrieve and RetrieveAndGenerate work. Permissions are metadata filters. Attributes are attached at ingestion, which for S3 means a .metadata.json sidecar next to each object, capped at 10 KB, and every query carries a filter expression. That mapping from users to filter values is a subsystem the team designs, builds and keeps correct.
Amazon Quick. The buy option, sold on per-user subscriptions. Point Quick at a managed knowledge base and it passes the signed-in user’s identity to Bedrock on every query, with no access-control configuration on the Quick side. It removes the build and the levers in the same move: no chunking choices, no embedding choices, no retrieval call to write against. Two limits bound it here. A Quick instance takes up to two managed knowledge bases, and it integrates with managed knowledge bases only, so anything living in a customer-managed one needs an integration the team writes anyway.
Self-run OpenSearch. The full-control end. Run the vector index directly, write the ingestion pipeline, and enforce permissions in application code before or after the query. It suits teams with retrieval requirements a knowledge base cannot express, such as unusual ranking or an index shared with non-RAG search. For a small team with a compliance-sensitive corpus it is the most rope and the least help.
A company already running Kendra has a fifth path: a Kendra GenAI index can serve as the retrieval source behind a Bedrock knowledge base, keeping its connector catalogue and its user-context filtering while application code moves to the knowledge base API. Kendra closed to new customers on 30 July 2026, so it is not selectable here, and it is left out of the table below.
Evaluation
Side by side
| Approach | Permissions crawled from the source | Enforced inside retrieval | Filters Salesforce per user | Chunking, embedding and vector store control | Answer with citations in one call | You assemble the app |
|---|---|---|---|---|---|---|
| Managed KB, ACL-aware | ✓ (S3 from a file you write) | ✓ | ✗ (connector carries no ACLs) | ✗ (embedding only) | ✓ (agentic retrieval) | ✓ |
| Vector KB, metadata filters | ✗ | ✓ (filters you map) | ✓ (via S3 or custom ingestion) | ✓ | ✓ | ✓ |
| Amazon Quick on a managed KB | ✓ | ✓ | ✗ | ✗ | ✓ (finished app) | ✗ |
| Self-run OpenSearch | ✗ | Your code | ✓ (you write ingestion) | ✓ | ✗ | ✓ |
Reading the table against the scenario: the team is building an application rather than adopting a finished one, which keeps them off Quick. No row filters all four repositories per user from crawled permissions, so the corpus splits. Three repositories have a managed connector with ACL awareness behind it. Salesforce has a managed connector with nothing behind it, which is worse than having none, because a non-ACL source sitting in the same knowledge base returns its documents to every caller. The deal notes go either through a vector knowledge base with filters, fed from S3 or custom ingestion, or through an export that lands them in S3 with ACL entries attached.
The solution
Put the HR documents, the Confluence runbooks and the shared-drive PDFs into a managed knowledge base with ACL awareness enabled on each data source, and keep the Salesforce deal notes out of it. They go to a second, vector knowledge base with metadata filters, reached by exporting the Knowledge articles into S3 or pushing them through custom ingestion, because the managed Salesforce connector crawls no ACLs and a new Salesforce connector on a customer-managed knowledge base is no longer supported. Three jobs follow: turning the enforcement on, feeding it a verified identity, and keeping the permissions true.
Turning it on differs by source. Confluence crawls its own restrictions during ingestion and re-checks them live at query time, so nothing is authored by hand. S3 takes an ACL file the team maintains: a global JSON array mapping key prefixes to entries of Name (an email), Type (USER), and Access (ALLOW or DENY), held in the same bucket as the content, with a per-document .metadata.json overriding the global file where one prefix is not enough. Every document needs an entry. An ACL-enabled S3 source skips anything without one, so a missing rule reads as a document that vanished rather than as a document anyone can see.
Feeding it an identity is where the security boundary actually lives. The application authenticates the user and passes the verified email in the user context on every Retrieve. Bedrock does not check that email against anything, so it has to come off the session, never off a client-supplied field. Two smaller rules follow. The address must match the one the source system holds, character for character, since there is no alias resolution. And every data source in a knowledge base needs ACL awareness enabled, because one source left without it returns its documents to every caller regardless of context.
Keeping it true is the part that separates a demo from a system. Crawled permissions and group memberships are only as fresh as the last sync, so the sync schedule is a compliance decision rather than a performance one. Real-time verification covers the gap for both Confluence editions, SharePoint, OneDrive and Google Drive; S3 and the custom source have none, so a tightening in the global ACL file reaches retrieval only once the affected prefix is reindexed. A per-document sidecar narrows that reindex to the one document, which is the argument for using sidecars where permissions move. Third-party identity provider credentials are cached for up to an hour, and permission changes are eventually consistent, usually landing within minutes. Test the loop adversarially before launch and after every change: a persona per group, a battery of queries aimed at the other groups’ material, zero cross-boundary results required.
Two design decisions sit underneath all of that. The content whose access genuinely varies document by document, the HR investigation notes, stays out of the index entirely; exclusion is the one permission strategy that cannot leak. And the Salesforce half keeps its metadata vocabulary small and coarse, mapped from containers rather than from per-record user lists, which drift immediately and outgrow what a filter expression can hold.
Worked example
Take the HR documents and the runbooks and trace the path end to end. The handbook lands under s3://kb-corpus/hr/handbook/ and the policies under s3://kb-corpus/hr/policies/, and the global ACL file grants each prefix to the groups that may read it:
[
{
"keyPrefix": "s3://kb-corpus/hr/handbook/",
"aclEntries": [
{ "Name": "dana@example.com", "Type": "USER", "Access": "ALLOW" }
]
}
]
A single document that needs an exception carries its own sidecar. severance-policy.pdf.metadata.json sits beside the object with an accessControlList array in the same entry format, and it takes precedence over the prefix rule. The investigation notes are never ingested; they stay in the HR system, and the assistant’s answer about them is that it cannot help.
The runbooks arrive through the Confluence connector with their space and page restrictions crawled alongside the content. Nothing is authored for them, and a restriction changed in Confluence after the last sync is caught by the live check at query time.
At query time an engineer signs in, the application resolves their verified email from the session, and the Retrieve call carries it:
{
"knowledgeBaseId": "KB12345678",
"retrievalQuery": { "text": "what is the on-call escalation path" },
"userContext": { "userId": "alex@example.com" }
}
Alex gets the handbook and the runbook spaces Confluence grants them. Dana in HR gets the handbook and the policies. Neither reaches the other’s restricted material, because the chunks never enter the context. When a persona test comes back empty and should not have, CheckIngestedDocumentAcl answers whether that user can reach that document, and GetIngestedDocumentAcl returns the whole ACL attached to it, which turns an absent result into a question with an answer. One innocent cause to rule out first: AWS warns that group membership for a user in a very large group can still be propagating after a sync reports success, and does not say how long, so retry before calling it a misconfiguration.
What’s worth remembering
- ACLs filter inside retrieval. Managed connectors crawl ACLs from S3, Confluence, SharePoint, OneDrive, Google Drive, Box and ServiceNow; Salesforce and the web crawler carry none.
- Vector stores trade ACLs. A customer-managed knowledge base takes only S3 and custom ingestion, and enforces permissions through metadata filters you map and maintain.
- Filtering is not authorization. Bedrock authenticates nobody; the application supplies a verified email from the session, matched exactly with no alias resolution.
- It fails closed. Deny beats allow, a document with no ACL is not ingested, and
Retrievewithout a user context returns nothing from ACL-enabled sources. - One unprotected source exposes its documents. ACL awareness switched off on any data source returns that source’s documents to every caller, whatever the user context.
- Sync schedule sets permission freshness. Confluence, SharePoint, OneDrive, Google Drive and Box re-check live; elsewhere changes wait for sync, so test a persona per group.