Threat model
v0.1.0. Review this document when a trust boundary changes; ADRs referenced throughout carry the detailed contracts.
What routeD is trusted with
routeD decides which model tier serves a request. It never holds provider credentials and never calls model providers (ADR-0001); the blast radius of a fully compromised routeD is therefore misrouting: sending a request to a cheaper / non-sovereign / lower-quality tier, failing to BLOCK, or denial of service. That is serious (sovereignty violations are the point of the product) but bounded: provider keys, billing and response content live in the gateway.
Assets, most valuable first:
- Routing integrity: the guarantee that hard constraints (data class, jurisdiction, CLOUD Act exposure, risk caps) are enforced before any optimisation (ADR-0003).
- Request content: prompts pass through routeD in both ingress modes. They must never be persisted or logged (invariant; enforced by review, the feedback-journal test asserts no request content lands on disk).
- Routing configuration: the four CRDs and the compiled snapshot.
- The decision record: headers / spans / journal used for audit.
Trust boundaries and threats
1. Caller -> router (untrusted)
Threats: header spoofing to reach a forbidden tier, prompt injection to influence classification, oversized / malformed bodies, path smuggling.
Mitigations: X-Routed-* request headers are untrusted and can only
tighten a decision; policy override requires overridable: true
(ADR-0007). Inbound x-routed-* is stripped before anything is forwarded
in both modes (tested inline, ext_proc and e2e). Bodies are size-capped
before parsing; unparseable bodies fail closed (400, nothing forwarded).
Paths are normalised and dot-segment / encoded-separator smuggling is
rejected; only the /v1 surface is forwarded by default. Classification
influences findings, but a classifier can only ever tighten what the
heuristics find (risk = max(model, heuristics), ADR-0016), and a degraded
classifier triggers the policy fallback, never a permissive guess
(ADR-0006).
Residual: a caller who can freely rephrase prompts can steer the task classification (e.g. phrase everything as "chat"); policies must not treat task labels as a security boundary. Data classes with header/PII detection plus hard constraints are the security boundary.
2. Router -> gateway
Threats: routeD's rewrite being ignored; response tampering.
Mitigations: the gateway is the enforcement point by design; integration
guides require honouring the rewritten model and failing closed
(failure_mode_allow: false). Upstream responses cannot spoof decision
headers (X-Routed-* from upstream is stripped; tested). routeD does not
inspect responses; response integrity is the gateway/TLS story.
Residual: a malicious gateway defeats routing entirely. The gateway is inside the trust base; choose and secure it accordingly.
3. Operator -> API server / router (control plane)
Threats: malicious or malformed CRDs; snapshot tampering in distribution; a compromised operator writing arbitrary cluster state.
Mitigations: one pure compiler validates everything (ADR-0008); the
admission webhook rejects bad CRDs with attributed diagnostics, fail-open
by default so it cannot lock out remediation (ADR-0015). Snapshots are
content-hashed; the router logs the hash on every swap and each decision
records snapshotHash for audit. Operator RBAC is minimal (read CRDs,
write status, one ConfigMap, one Lease); the router has no Kubernetes
API access at all (ADR-0014). gRPC snapshot distribution is in-cluster
plaintext in v0.1.0 - see Gaps.
Residual / gaps: anyone with CRD write access controls routing; that is the
intended administrative boundary - protect it with Kubernetes RBAC. The
snapshot gRPC stream serves plain TCP by default; enable mutual TLS with
--snapshot-tls-dir on both sides (ADR-0021), or restrict the operator's
9090 to router pods with a NetworkPolicy.
4. Model artifacts (supply chain)
Threats: a swapped classifier or learned-router model silently loosening routing; registry compromise; cache poisoning.
Mitigations: every remote artifact is digest-pinned and verified before
use, cache hits re-verify, mismatches never enter the cache (ADR-0016).
oci:// pulls verify the manifest against the pinned digest and the layer
against the manifest (ADR-0019), and with ROUTED_ARTIFACT_COSIGN_PUB
set, every oci:// artifact must additionally carry a valid cosign
signature by the configured key, checked before any bytes are fetched
(ADR-0022; fail closed). A configured model that fails to load
stops startup rather than degrading silently. Structurally, models can
only tighten decisions: classifier risk is floored by the heuristics, and
learned-router predictions only refine predictedQuality inside the
engine after hard constraints.
Residual: digest pinning authenticates content, not choice: whoever writes the RouterProfile chooses the model. That is again the CRD-write administrative boundary.
5. Release artifacts
Threats: tampered images / chart; dependency compromise.
Mitigations: images are cosign-signed (keyless, CI identity) with CycloneDX
SBOMs; cargo deny gates advisories and licenses in CI; the toolchain and
runtime images are digest-pinned Debian releases in lockstep (ADR-0002,
ADR-0019). Builds are reproducible from a tag in CI only.
6. Telemetry, feedback and the learning loop
Threats: prompt leakage through logs / spans / journal; feedback poisoning to degrade future routing; disk exhaustion.
Mitigations: prompts are never logged; the optional prompt hash is salted and off by default. The decision journal carries closed-vocabulary labels only (tested). Feedback never changes routing online (ADR-0018); poisoned feedback can only degrade a future, offline-trained model, which ships through the same review-and-pin pipeline as any artifact and still cannot loosen hard constraints. Journal writes are bounded and drop-not-block. Client-controlled strings are length-capped before reaching logs or spans.
Residual: feedback is unauthenticated beyond the platform's ingress controls; rate limiting and caller auth are the gateway's job (ADR-0001). Journal files grow unbounded on the emptyDir; rotate or ship them.
7. Denial of service
Body caps before parsing, classification behind a bounded semaphore with strict timeouts, streaming idle watchdogs, bounded decision-header size, and fail-closed readiness. routeD inherits the platform's L4/L7 protections for volumetric attacks.
Known gaps (tracked, post-v0.1.0)
- Webhook
failurePolicy: Failguidance for installations that want hard admission gating (default stays Ignore, ADR-0015). - Credentialed (private) OCI registries for model artifacts.