Threat model
The full document lives in the project's design repository; this is the summary a user needs.
What Keryx is, in security terms
An in-cluster service that reads a customer's telemetry, sends it to a third-party LLM, and publishes conclusions to Slack and a web page. Three sentences generate most of the model:
- Keryx executes no writes. Its value proposition is a negative capability, so the interesting failures are the ones where a write becomes possible.
- Reading is exporting. Everything Keryx reads leaves the cluster — to the LLM provider on every model call, and to Slack as evidence excerpts. A read-only agent is still an exfiltration channel.
- Its inputs are attacker-influenced by design. An investigator that only ever read trusted input would not be investigating.
Trust boundaries
| # | Boundary | Crossing |
|---|---|---|
| B1 | Cluster API ↔ Keryx | Read-only ServiceAccount, namespace-scoped |
| B2 | Telemetry ↔ the model's context | Nonce-fenced evidence blocks |
| B3 | Keryx ↔ LLM provider | Everything read, over the network, BYOK |
| B4 | Keryx ↔ Slack / web page | Evidence excerpts and finding prose |
| B5 | Keryx ↔ a third-party agent | keryx mcp, read-only, fenced |
| B6 | Registry / CI ↔ cluster | Images and charts Flux pulls |
Cluster mutation
Enforced at the RBAC layer, not the tool layer. The ServiceAccount has no
write verbs; the GitHub token is read-only and fine-grained. MCP read-only
flags are defense in depth — CVE-2026-46519 hit a Kubernetes MCP server
whose flag was enforced at tools/list but not tools/call. A denylist
test covers every mounted server's write-capable tools.
Prompt injection
Tool output is wrapped in nonce-fenced blocks the system prompt marks instruction-inert. The model calls parameterised MCP tools only — no shell, no raw kubectl, no arbitrary URLs — and ANSI/control characters are stripped.
keryx.md, the small always-loaded environment document, is treated as
semi-trusted: it is cluster-resident config injected into every prompt,
so a compromised repo or a careless edit would otherwise poison every
investigation. It is fenced like telemetry.
The honest status. The defenses are tested: an injection suite drives the whole loop with a scripted model that obeys the injection, plus a 124-tool denylist sweep and the chart's RBAC assertions, all green in CI. Two live injection scenarios are specified and have never been run end to end against a real model. Until they have, the claim to make is "the defenses are tested" — not "the agent has been tested against injection."
Exfiltration
Redaction happens at the Slack boundary, opt-in and off by default; stored evidence stays verbatim. That asymmetry is deliberate: core-api enforces citations as verbatim substrings of stored output, and the eval harness verifies citations by re-execution, so redacting at capture would break both. The operator configures what leaves the cluster.
Never create a second incident
Keryx runs inside a cluster that is already having a bad day. Query-cost caps, a per-investigation token budget shared across retries, a wall-clock cap, and alert-storm coalescing all exist so an investigation cannot become the outage.
Residual risks we publish
The threat model carries its own register of what is designed but unbuilt, and what is claimed but unproven. The largest entry is the one above: the live injection evals. A threat model whose residual register is empty is a threat model nobody finished reading.