Connecting an assistant to company files is an access-control decision before it is an AI decision. This paper sets out the failure modes of conventional approaches, the properties a trustworthy design needs, and how K-Lake enforces them.
When an assistant answers from company files, the question that matters is not "is the answer good?" but "could this person have opened the files it came from?" If the answer to the second question is ever no, the assistant has become a way around access control, and no quality of answer makes up for that.
Conventional search and retrieval products apply access control in application code, filtering results after they are fetched. That design leaks: one missed clause, one new endpoint, one bulk export path, and files reach people who should not see them. This paper argues that per-file trimming must be enforced where the data is stored, that every caller must be a named identity, and that every access must be recorded. It describes how KDBL Context Lake (K-Lake) does each of these, and closes with questions a reviewer should put to any vendor.
A file share with thirty years of ACLs is, whatever else it is, a working access-control system. A person who cannot open a folder cannot read what is in it. A search index changes that: it reads every file once, with a privileged account, and then answers queries. Whether the person asking could have opened the file is now a property of the index, not of the share.
An assistant sharpens the risk in three ways.
None of this is a reason not to connect an assistant. It is a reason to connect it as the user, not as a service account, and to enforce that below the layer where mistakes are made.
The common design is to index everything with a privileged crawler, then filter results in the application: fetch the candidate documents, check each against the caller's groups, drop the ones they may not see. It is intuitive, it is easy to demonstrate, and it fails in production for structural reasons.
Search, retrieval, "related documents", entity lookups, export, the admin report, the new MCP tool added last sprint. Each is a path that must apply the filter correctly. The guarantee is only as strong as the most recently written one. A single missed clause returns a row that should not have been returned, and nothing in the architecture notices.
Filtering documents does not filter the counts, facets and graphs derived from them. "This supplier appears in 40 documents" tells a user something about 39 documents they cannot open. Application-layer designs rarely trim aggregates, because the filter lives on the document endpoint and the aggregate was computed elsewhere.
When the filter is code, the absence of the filter is access. A new feature that forgets to call it ships open. Reviewers can only find this by reading every path, on every release.
Application-layer filtering must be applied correctly in every code path that touches data. Database-layer enforcement inverts this: access is denied by default and granted only by an explicit policy, so new code inherits the controls and fails closed.
Access policy must be evaluated by the datastore on every query, for every table, so that no request path can return a row the caller is not entitled to. The application should run under a role that cannot bypass the policy.
The permissions on the file are the organisation's decision about who may see it. The index must capture them, correlate them to directory identities, and enforce exactly those, not a simplified model rebuilt by hand.
Shared service accounts collapse everyone into one principal, and trimming per user becomes impossible. Sign-in must be federated to the organisation's identity provider; any token must be tied to exactly one user.
An assistant must authenticate as the person using it and inherit that person's trimming. A "connector" running as a privileged account is, by definition, a bypass.
Every query, every file returned, every open of an original, with the principal and the outcome. Citations must link to the original and re-check permission at open time, so an auditor can trace any answer to its evidence.
K-Lake runs entirely inside the customer's Kubernetes cluster. The components are an API, a web console, stateless crawl and extraction workers, an optional MCP server, a managed PostgreSQL database and a work queue. The customer's identity provider and content sources stay under the customer's control; K-Lake reads sources read-only and federates sign-in.
Tenant isolation and per-file trimming are enforced with native PostgreSQL row-level security. Each request runs in a database session bound to its authenticated tenant and principal. Policies attached to every table restrict visibility to rows that tenant may see and, within it, to files whose captured permissions grant that principal access. The API answers requests over a database role that cannot bypass these policies, so no request path can return tenant or file data without satisfying them. The crawl and extraction workers, which write the index and answer no caller traffic, run with a privileged role.
Because the boundary lives below the application, a bug in application code cannot widen it. New endpoints, new MCP tools and new reports inherit the policies automatically. Entity counts and Discover aggregates obey the same trimming, with tenant-wide totals reported separately and explicitly where that is useful.
At crawl time, K-Lake captures NTFS and NFSv4 ACLs and POSIX ownership from shares that expose them. Directory enrichment correlates the native principals, such as SIDs and UIDs, to the identities and groups in the customer's directory, through Microsoft Entra, Active Directory over LDAP, or a declared mapping. Well-known groups such as Everyone and BUILTIN\Users are handled explicitly. The result is a per-file grant set that the row-level policies evaluate.
Only the callers the customer deliberately appoints see a whole source regardless of per-file permissions: tenant administrators, and a source's own owners and editors. Where a share does not carry per-file permissions, or a source cannot expose them, the customer grants access at the source level to the principals they choose.
The services and all source connectors are written in Rust. Buffer overflows, use-after-free and data races, the classes behind a large share of vulnerabilities in connectors written in C and C++, are eliminated at compile time. The result is a small, statically analysable codebase with a reduced exploitable surface.
K-Lake holds no passwords. Users sign in through the customer's own identity provider over OIDC and OAuth 2.1: Microsoft Entra ID, Okta, Google, Keycloak, or any compliant provider. Each token is checked against the issuer and audience configured for that tenant, against its expiry, and against the provider's published signing keys.
For the CLI and API, personal access tokens are minted per user, shown once, optionally given an expiry, and revocable. A token acts as its user: it carries exactly that user's tenant, groups and trimming.
The MCP server is a full OAuth 2.1 authorization server. It presents its own authorize, token and registration endpoints, signs the access tokens it issues, and federates the login to the customer's identity provider. A token presented to the MCP endpoint must be addressed to the MCP resource itself; a token minted for the REST API is rejected there unless an operator deliberately enables that fallback. Each tenant has its own endpoint and sign-in issuer, so a token resolves to exactly one tenant and is refused at every other. Assistants connect as the individual user and inherit their trimming.
Secrets are protected before they reach the database. Source credentials, identity-provider client secrets and tokens are sealed with authenticated encryption under a master key that is never itself stored in the database and is supplied from a Kubernetes Secret or an external secret manager. A stored secret is write-only: no read API returns it. Credentials are never accepted on the command line, so they do not reach shell history or logs.
Every AI query over MCP, each tool call including those denied, and every open of an original file is recorded with the acting principal, the tenant, the sources and files returned, the client address and the outcome. The record is queryable from the console, the CLI and the API, and emitted to the structured log stream for the customer's SIEM. Any file whose content is sent to an external extraction provider, where a customer chooses to configure one, is recorded with the file, the provider and the bytes sent.
Every search result and every citation links back to the original file and re-checks the caller's permission at open time. A built-in diagnostic answers "why can't this user see this file?" by walking the captured permissions, the directory correlation and the resulting grant, so an access question can be settled with evidence rather than argument.
The tenant administrator's MCP view shows the live access flow, from users through clients and tools to sources, and allows any session to be revoked and any source to be hidden from AI access while remaining searchable in the console.
These are the questions we would put to any product that offers to connect an assistant to company files, including ours.
| Question | K-Lake |
|---|---|
| Where is per-file access control enforced? | In the database, by row-level security, on every query. The API's database role cannot bypass it. |
| Whose permissions are enforced? | The file's own, captured from NTFS, NFSv4 and POSIX and correlated to the customer's directory. |
| Does the assistant connect as the user or as a service account? | As the user, through OAuth 2.1 federated to the customer's identity provider, or with a token tied to one user. |
| Are aggregates and graphs trimmed too? | Yes. Entity results and estate views obey the same policies; tenant-wide totals are reported separately and labelled. |
| What is recorded? | Every AI query, every denied call, every open of an original, with principal, tenant, files and outcome. |
| Can a citation be verified? | Every citation links to the original and re-checks permission at open time. |
| Does file content leave the boundary? | Not for extraction, search or indexing. All run in the customer's cluster. The product does not phone home. |
| What language is the core written in? | Rust, for the services and every connector. |
| How is the software itself scanned? | A rolling daily security report across every shipped image; signed release images; minimal containers. |
K-Lake's complete security overview, written for this audience, is maintained in the product documentation and kept in step with each release. Vulnerability reports go to security@kdbl.com.
We would rather answer your security questions directly. Ask for a review session, or evaluate K-Lake on a single VM and test the trimming yourself.
Talk to us Read the security overview