Security overview¶
This page is written for security, risk, and compliance teams evaluating KDBL Context Lake (K-Lake). It explains the trust model, how isolation and access control are enforced, and the controls that protect data in transit, at rest, and in use.
At a glance
- Self-hosted in your Kubernetes cluster — data stays inside your boundary, and the product does not phone home. The exceptions are opt-in and off by default: an administrator can switch on cloud extraction for a source, which sends only the pages the standard extractor cannot read to a document service in your own cloud account, with per-source consent and an egress audit; and a tenant administrator can switch on AI help for database models, which sends a model's structure (and, only if Ask is also allowed, the results of the queries it runs) to your own AI provider, budgeted and audited.
- Tenant isolation and per-file access control are enforced in the database with native row-level security (RLS), not in application code.
- Memory-safe core written in Rust, eliminating entire classes of memory-corruption vulnerabilities.
- Identity-first: every caller is a named principal — signed in through your own identity provider (OIDC / OAuth 2.1), or holding a personal access token tied to one named user.
- Auditable: every AI query over MCP is recorded with the principal, tenant, and the sources and files it returned; every state-changing admin action is recorded in an append-only admin audit log; every service emits structured logs and metrics for your own monitoring stack.
- Continuously scanned: a rolling daily security report publishes vulnerability findings across all shipped container images.
Security architecture¶
K-Lake runs entirely inside your infrastructure. The only components are the API, the web console, a fleet of stateless workers, the MCP server (optional), a managed database, and a separate work queue. Your identity provider and your content sources remain under your control; K-Lake reads from sources read-only and federates sign-in to your IdP.
flowchart TB
subgraph client["Client zone"]
U["Operators and AI assistants"]
end
subgraph cluster["Your Kubernetes cluster — primary trust boundary"]
direction TB
IN["Ingress / TLS termination"]
API["K-Lake API"]
WC["Web console"]
MCP["MCP server<br/>(OAuth 2.1 authorization server)"]
W["Workers<br/>(stateless)"]
subgraph data["Data layer"]
DB[("Managed database<br/>PostgreSQL · RLS")]
Q[["Work queue<br/>(task references only)"]]
end
end
subgraph ext["External — customer-controlled"]
IDP["Identity provider<br/>OIDC / OAuth 2.1"]
SRC[("Content sources<br/>S3 / Azure Blob / SMB / NFS / OneDrive")]
end
U -->|HTTPS| IN
IN --> API
IN --> WC
IN --> MCP
API --> DB
MCP --> DB
W --> DB
API --> Q
W --> Q
W -->|read-only| SRC
API -. "federated sign-in" .-> IDP
MCP -. "federated sign-in" .-> IDP
Trust boundaries. The cluster is the primary trust boundary. All ingress is TLS-terminated. The database is the policy decision and enforcement point for everything a caller can reach: the API answers requests over a database role that cannot bypass row-level security, so no request path can return tenant or file data without satisfying the policies. (The crawl and extraction workers, which write the index and answer no caller traffic, run with a privileged role.) The work queue holds only task references (source ids, paths), never file content or credentials.
Improvements on prior approaches¶
K-Lake was designed to close gaps that are common in conventional indexing and enterprise-search products.
| Concern | Conventional approach | K-Lake approach |
|---|---|---|
| Access control | Filtering applied in application code — a single missed WHERE clause leaks data |
Row-level security in the database — enforced on every query, fail-closed by default |
| Multi-tenancy | Shared tables with app-side tenant filters | Tenant isolation enforced by RLS in the database |
| Memory safety | C/C++ connectors prone to buffer overflows and use-after-free | Rust — memory-safe by construction, no garbage collector |
| Identity | Bolt-on auth or shared service accounts | Native per-user OIDC / OAuth 2.1, federated to your IdP |
| AI access | Bulk export of data to a model | MCP with per-user trimming and citations — data stays in your cluster |
| Deployment | SaaS; data leaves your boundary | Self-hosted in your cluster; air-gap capable; no phone-home |
Memory-safe foundation (Rust)¶
The core services and all source connectors are written in Rust. This is a deliberate security decision:
- Memory safety by construction. Rust's ownership model eliminates buffer overflows, use-after-free, and data races at compile time — the vulnerability classes behind a large share of CVEs in systems software.
- No garbage collector, so performance is predictable and there is no GC-pause attack surface; resource cleanup is deterministic.
- Strong typing and exhaustive error handling reduce the logic errors that lead to unsafe states.
The result is a small, statically-analysable codebase with a reduced exploitable surface compared to memory-unsafe alternatives.
Multi-tenant isolation¶
Every source, user, token, and indexed file belongs to exactly one tenant. Isolation is not a matter of careful application coding — it is enforced by the database itself.
flowchart TB
T1["Tenant A request<br/>(authenticated principal)"]
T2["Tenant B request<br/>(authenticated principal)"]
RLS{{"Row-level security<br/>enforced in PostgreSQL"}}
T1 --> RLS
T2 --> RLS
RLS -->|"tenant = A"| DA[("Tenant A rows only")]
RLS -->|"tenant = B"| DBB[("Tenant B rows only")]
RLS -. "cross-tenant access" .-> X["Denied by default"]
Each request runs in a database session bound to its authenticated tenant and principal. RLS policies attached to every table restrict visibility to that tenant's rows. A bug in application code cannot widen this boundary, because the boundary lives below the application, in the database.
The same boundary extends to AI access. Each tenant has its own MCP endpoint and its own sign-in issuer, so a token is resolved to exactly one tenant and is refused at every other tenant's endpoint; a tenant can federate to its own identity provider and sign its tokens with its own keys, and its administrators can see and change only their own. Deleting a tenant ends its endpoint and every token minted for it in the same moment.
A tenant can be suspended — every sign-in path refuses its users and its ingest stops, while its data is kept — and resumed. Deletion is asynchronous: the tenant is locked out at once and its data is purged in the background, with progress reported until it is gone. An offboarding export hands a departing tenant its data first. See Multi-tenant deployments and Hosting many customers.
Authorization & security trimming¶
Beyond tenant isolation, K-Lake enforces per-file access control. A caller sees a file only if the file's source-native permissions (NTFS / NFSv4 ACLs, POSIX ownership) grant their identity access. These native principals are correlated to your IdP identities by directory enrichment, then enforced — again — by row-level security at the database layer.
sequenceDiagram
participant C as "Client (user / AI)"
participant API as "K-Lake API / MCP"
participant IDP as "Identity provider"
participant DB as "Database (RLS)"
C->>API: Request + OIDC bearer token (or PAT)
API->>IDP: Fetch the IdP's published signing keys (cached)
IDP-->>API: Signing keys
Note over API: Verify signature, issuer,<br/>audience and expiry; read the<br/>principal and groups from the token<br/>(a PAT needs no IdP call at all)
API->>DB: Query in a session bound to tenant + principal
Note over DB: RLS policies filter by<br/>tenant AND per-file ACL grants
DB-->>API: Only rows the principal is authorized to see
API-->>C: Authorized results (MCP calls recorded in the audit trail)
Because trimming is enforced where the data lives, the same guarantee applies uniformly across the web console, CLI, REST API, and AI access over MCP: connecting an assistant does not widen anyone's access, because every answer is trimmed to what that person could already open. The only callers who see a whole source regardless of per-file permissions are the ones you deliberately appoint: tenant administrators, and a source's own owners and editors. See Per-file security trimming and Directory enrichment for detail.
Why database-layer enforcement is stronger
Application-layer filtering must be applied correctly in every code path that touches data; one oversight leaks records. Database-layer RLS inverts this: access is denied by default and granted only by an explicit policy, so new code paths inherit the controls automatically and fail closed.
Identity & access management¶
- Federated sign-in. Users authenticate through your own identity provider via OIDC / OAuth 2.1. K-Lake does not become a parallel identity store; it holds no passwords and consumes verified identities from providers such as Microsoft Entra ID, Okta, Google, or Keycloak. Each token is checked against the issuer and audience configured for that tenant, against its own expiry, and — for its signature — against the provider's published signing keys. A token must name its issuer and audience: one that leaves either claim out is refused even when the provider signed it, so a token issued for another application on the same provider cannot sign anyone in.
- Personal access tokens (PATs). For programmatic and CLI use, PATs are minted per user, shown once, optionally given an expiry, and revocable. A PAT acts as its user: it carries exactly that user's tenant, groups and per-file trimming, and nothing more.
- AI access over MCP. The MCP server is a full OAuth 2.1 authorization
server (per the relevant RFCs for protected resources and audience
restriction): it presents its own
/authorize,/tokenand/registerendpoints, signs the access tokens it issues, and federates the login itself to your 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. An assistant can equally authenticate with a personal access token and no identity provider at all — the path for local evaluation and air-gapped sites. Either way, assistants connect as the individual user, inherit that user's per-file trimming, and receive a citation for every fact — so answers are grounded and verifiable, and no data is exported to the model provider in bulk. - An MCP endpoint hardened for the internet. When remote access is on, a dedicated gateway exposes only the MCP, sign-in, discovery and download paths — never the admin API — behind per-address request and connection limits. K-Lake holds each user to a request rate, throttles failed sign-ins per address and per token, and refuses the cluster-admin token on the MCP endpoint outright. A personal access token must carry a scope that permits MCP; an admin-only automation token is refused there. See Exposing MCP to the internet.
See MCP server and Connect your AI.
Encryption¶
| Layer | Control |
|---|---|
| In transit | TLS for all client-to-service traffic; TLS for service-to-database connections. |
| At rest — secrets | Source credentials, IdP client secrets, and tokens are encrypted with authenticated encryption (AEAD) before they reach the database, under a master key that is never itself stored in the database. Each secret is sealed per tenant — with a per-tenant key derived from the master key — and bound to the row it belongs to, so a stored secret opens only in its own tenant and row: one copied into another tenant, or another row, does not decrypt. A stored secret is write-only: no read API ever returns it, only whether it is set. |
| At rest — datastore | Deployable on encrypted volumes for full at-rest encryption of the managed database and its backups. |
| Key handling | The master key is supplied to the services from a Kubernetes Secret or an external secret manager, and is re-read per use, so a rotation needs no restart. Without it, a service simply cannot store or decrypt a secret — the operation is refused. |
Credentials are never written to logs or shell history (secrets are read from stdin or the environment, never command-line flags).
Auditability & observability¶
- Audit trail. Every AI query over MCP — each tool call, including the ones that were denied — and every open of an original file, whoever initiated it, is recorded with the acting principal, tenant, the sources and files returned, the client address, and the outcome, enabling forensic review and access reporting. Any file whose content is sent to an external extraction provider is recorded the same way, with the file, the provider, and the number of bytes.
- Admin audit log. Every state-changing API request — a tenant created, a
role changed, an identity provider repointed, a source added, a token minted —
leaves one append-only record, including requests that were refused: who
made it (principal and email) and how they held the authority, what was
changed, for which tenant, and the outcome. Tenant administrators see their
own tenant's records; export it over the API (
GET /api/audit/admin, with?format=csv). No request body is stored, so no secret ever reaches the log. - Structured logs & metrics. Services emit structured logs and metrics in standard formats, so they integrate with your existing SIEM and monitoring stack (e.g. Prometheus-compatible metrics, log shippers to your aggregator). See Telemetry.
- Verifiable retrieval. Every search result and AI citation links back to the original file, re-checking the caller's permissions at open time — so an auditor can confirm exactly where any answer came from.
Supply-chain & vulnerability management¶
- Daily security report. K-Lake publishes a rolling daily security report with automated vulnerability scan results across every shipped container image.
-
Signed images with provenance. Every release image is signed with cosign; the public key is published in the releases repository, so you can verify an image before you deploy it:
cosign verify --key cosign.pub ghcr.io/kdbl-consulting/kdbl-worker:
Images are also built with provenance attestations and a software bill of materials, and the release pipeline checks that the image it publishes was built from the tagged commit. - Dependency advisories, daily. The service's Rust dependency tree and the console's shipped JavaScript dependencies are each audited daily against the advisory databases, failing on anything not consciously excepted with a dated, written reason. - Minimal images. Containers ship only what each service needs, reducing the attack surface and patch burden.
OWASP MCP Top 10¶
Every release is reviewed against the OWASP MCP Top 10 (2025) before it is tagged, and the result ships in that release's notes, item by item, so you can see what changed between versions. The standing position is:
| Item | Status | In short |
|---|---|---|
| MCP1 Token mismanagement and secret exposure | Covered | Tokens stored only as hashes; one-hour access tokens with rotating refresh tokens and family revocation on replay; revoking a session or removing a client also refuses the access tokens it already issued; revocation holds on every server within seconds; personal access tokens always expire (one year by default, configurable); all stored secrets, including the AI provider key for database model help, sealed per tenant and never returned; the AI provider client follows no redirects, and a gateway must be on the deployment's allow-list. |
| MCP2 Privilege escalation via scope creep | Covered | Every tool is read-only; tools a user may not use are not advertised and are refused; token scopes are enforced at the MCP endpoint for OAuth tokens and personal access tokens alike, and a client asking for an unknown scope is refused at sign-in; where a database connection has several models, an assistant is offered only the published models the user's groups admit. |
| MCP03 Tool poisoning | Covered | A fixed set of tools defined in code, no third-party or network-loaded tools; wording changes only by administrators, validated, audited and announced to clients. |
| MCP4 Software supply chain | Covered | Locked dependencies, daily advisory gates on both the service's and the console's shipped dependencies, every image scanned before release and daily afterwards, provenance attestations, SBOMs, and cosign-signed release images. |
| MCP5 Command injection and execution | Covered | No tool runs a shell command; every value is a bound parameter; database questions compile from the semantic model, raw SQL is off by default, only a tenant administrator can switch it on, and it is validated and bounded when on; model-authoring features use the same bounded, parameterised, audited path, for administrators only. |
| MCP6 Prompt injection via contextual payloads | Covered | Retrieved text is labelled as data in every tool result, the tool descriptions and server instructions tell the assistant to follow only the user's instructions, and control and bidirectional-override characters (including invisible "tag" characters) are stripped before the assistant sees it; tools are read-only, results are permission-trimmed, output is capped. With AI help for database models off (the default), nothing server-side acts on content. Switched on, it sees a model's structure only, and its suggestions apply only when an author accepts them; with Ask also allowed, the query results it sees are labelled as data, stripped the same way and scrubbed of connection details, and it can only run further read-only queries on that model, at most six per question within a minute, each logged. |
| MCP07 Insufficient authentication and authorization | Covered | OAuth 2.1 with PKCE required, resource indicators, per-tenant issuer and keys, audience-bound tokens; every query runs under row-level security as the calling user. |
| MCP8 Lack of audit and telemetry | Covered | Every call, denial and authentication failure recorded and streamable to your SIEM; MCP audit rows are append-only for the application role, as the admin audit log is; every AI help and Ask call on a database is audited with what was sent and the tokens used, refused calls included; a refused token from a revoked session is recorded under the caller's organisation, and why each AI client's latest sign-in or refresh was refused is kept per client and included in diagnostic packs, which never carry tokens, secrets or search queries. |
| MCP9 Shadow MCP servers | Covered | One endpoint, off by default, enabled only by the deployment administrator; no default credentials; clients and sessions visible and revocable. |
| MCP10 Context injection and over-sharing | Covered | No server-side session state; per-user caches tested for leaks; permission-trimmed results; signed, expiring citation links that re-check permission when opened; database data reaches an AI provider only through Ask, which a tenant administrator must allow separately and is off by default. |
This table reflects release 0.1.52 and later, reviewed on 2026-10-14; the notes of each release carry the status at that version. The engineering assessment behind this table, with the controls and the tests that exercise them, is maintained with the product and updated for every release. Ask your KDBL contact for the current copy.
Deployment & data residency¶
- Runs in your cluster. K-Lake deploys into your own Kubernetes environment; all content and metadata stay within your boundary. The exception is opt-in and per source: with cloud extraction switched on and consented to for a source, the pages the standard extractor cannot read are sent to a document service in your own cloud account, and every file sent is recorded in an egress audit. Likewise opt-in, per tenant and off by default: AI help for database models sends a model's structure, never its data, to your own AI provider; only Ask, a separate switch, sends the results of the queries it runs. Every call is recorded in the database's query log.
- Air-gap capable. K-Lake can run with no internet egress — including fully on-prem AI retrieval over MCP — so regulated and disconnected environments are first-class. See the air-gapped demo.
- Offline licensing. Licensing is enforced offline; there is no license-server call-home.
Data protection & compliance¶
KDBL Consulting is registered with the UK Information Commissioner's Office (ICO) under registration ICO:000014563614. Because K-Lake is self-hosted in your own infrastructure, your content and metadata remain under your control and within your data-residency boundary — KDBL does not process or receive your indexed data.
Reporting a vulnerability¶
If you believe you have found a security issue, please email security@kdbl.com or contact your KDBL representative. Please do not disclose publicly until we have confirmed a fix.