Daevix Docs

Threat Model

Open as Markdown

Threat actors, trust boundaries, data flows, and STRIDE analysis for the Daevix platform.

Threat Model

This document identifies who would attack the platform, where trust boundaries exist, what data crosses those boundaries, and what threats apply to each flow. For the platform’s security posture and controls, see Security Model.

Scope

In scope: Control plane, enclave, LLM proxy, agent containers, database, Redis, SSE event stream, and all data flows between them.

Out of scope: External IAM provider internals, upstream LLM provider (Anthropic API) internals, and the operator’s broader cloud/network infrastructure.


Assets

What an attacker actually wants, roughly ordered by blast radius:

AssetWhere it livesWhy it matters
IAM signing key (ES256)IAM service; only the public half is published via JWKSRoot of trust for every platform and operator JWT - compromise forges arbitrary identities org-wide, not just one agent
Enclave API key (SHA-256 hashed)Enclave processGrants the enclave’s full capability set - impersonate any of its agents, forge JWTs, decrypt secrets in transit
Refresh token (24h, single-use, family-rotated)Sidecar-only volume, never mounted into the main containerLong-lived; theft lets an attacker mint new platform JWTs repeatedly until rotation/replay detection catches it
Agent/org secrets (API keys, credentials)Encrypted at rest (AES-256-GCM, DEK/KEK); decrypted only at deliveryDirect access to whatever third-party system the secret unlocks
Platform JWT (5-min, ES256)Agent main container, read-only volumeGrants agent identity to the Enclave/LLM Proxy for the token’s remaining lifetime
Database records (agents, secret ciphertext, audit logs, iam_* tables)PostgreSQLCentral store - a database attacker can reach most of the above at rest
Redis state (JTI blacklist, SSO sessions, rate-limit counters)Redis (optional component, deployed as separate broker-local and control-plane instances - see DF-14/DF-15)Backs instant revocation and session validity; write access can un-revoke a token or forge a session
LLM audit log (full request/response bodies)llm_audit_log tableMay contain PII, credentials pasted into prompts, or proprietary content from every agent conversation
Bootstrap token (single-use, 1h)Plaintext env var at container creationOne-time claim of a freshly created agent’s identity
Policy definitions & approval recordsDatabase (policy_decisions)Governs which tool calls agents may execute; tampering changes what actions are permitted

Assumptions

  • TLS is correctly configured and not intercepted at any boundary; several entries below (DF-2, DF-3) explicitly degrade to plaintext-secret exposure if this breaks (e.g. --insecure-skip-tls).
  • The enclave/broker host OS and its Podman or Kubernetes control plane are not themselves compromised - the “Compromised Enclave” actor is analyzed as already holding enclave credentials, not as a host-level privilege escalation starting from nothing.
  • The database and Redis (where deployed) are reachable only from platform services, never directly from agent containers or the public internet - the “Database Attacker” actor and the Redis threats in DF-14/DF-15 assume access came via a compromised service or leaked credential, not an open port.
  • On the Kubernetes backend, kata-containers VM isolation (kata-clh) actually contains a container escape at the hypervisor boundary. Kata is mandatory for every agent pod, so DF-10 treats that VM boundary as the containment guarantee - there is no in-pod hardening layer behind it - and does not separately model a kata/cloud-hypervisor escape.
  • Bootstrap and refresh tokens are stored only as specified (SHA-256 hashed at rest, single-use) and operators do not copy them into logs, tickets, or other systems outside the platform.

Threat Actors

ActorStarting AccessMotivation
Compromised AgentMain container holding the delivered platform JWT (read-only) and secrets; the refresh token lives on the sidecar-only volume. Network access (forced through the proxy) to the enclave and proxy.Escalate privileges, access other agents’ data, exfiltrate secrets, pivot to internal services.
Malicious OperatorValid IAM JWT scoped to an organization. Access to the operator API.Access another organization’s data, escalate within the org, abuse destructive operations.
External AttackerNo credentials. Network access to public-facing endpoints.Steal tokens, exploit unauthenticated endpoints, man-in-the-middle, denial of service.
Compromised EnclaveEnclave API key, ECDSA signing key, secrets decryption key, database credentials. All agent secrets pass through in cleartext during delivery.Impersonate any agent, forge JWTs, intercept secrets, manipulate provisioning.
Database AttackerRead or write access to PostgreSQL.Extract secrets, forge tokens, manipulate agent state, access cross-tenant data.

Trust Boundaries

┌─────────────────────────────────────────────────────────┐
│                    Operator Network                      │
│                                                          │
│  ┌──────────┐     ┌────────────────┐     ┌───────────┐  │
│  │ dvx CLI  │────▶│ Control Plane  │◀────│    IAM    │  │
│  │(operator)│     │                │     │           │  │
│  └──────────┘     └───────┬────────┘     └───────────┘  │
│                           │ SSE + API                    │
│       ┌───────────────────┼──────────────┐               │
│       │                   ▼              │               │
│       │           ┌──────────────┐       │               │
│       │           │    Enclave   │       │               │
│       │           └──┬───────┬──┘       │               │
│       │              │       │          │               │
│       │      ┌───────┘       └────┐     │               │
│       │      ▼                    ▼     │               │
│       │ ┌─────────┐      ┌───────────┐  │               │
│       │ │  Agent  │      │ LLM Proxy │──┼──▶ Anthropic  │
│       │ │(untrust)│      │           │  │               │
│       │ └─────────┘      └───────────┘  │               │
│       │                                 │               │
│       │         Agent Network           │               │
│       └─────────────────────────────────┘               │
└─────────────────────────────────────────────────────────┘
BoundaryBetweenAuthentication
B1Agent ↔ EnclaveBootstrap token (single-use), then platform JWT (ES256, 5min)
B2Agent ↔ LLM ProxyPlatform JWT (ES256, 5min) or netproxy-forwarded service-signed identity headers
B3Enclave ↔ Control PlaneEnclave API key (SHA-256 hashed)
B4Enclave ↔ Compute RuntimeUnix socket (Podman) or service account (Kubernetes)
B5Operator ↔ Control PlaneIAM JWT (ES256, JWKS-validated)
B8LLM Proxy ↔ Upstream LLMResolved API key (injected from encrypted storage) or agent’s own credentials (passthrough mode)
B9All services ↔ DatabaseConnection string credentials
B10Agent cert ↔ Platform CAX.509 client certificate (ECDSA P-256, 5min, issued by enclave CA)
B11Broker ↔ Redis; Control Plane/IAM ↔ RedisConnection string credentials (optional component; broker-local and control-plane/IAM instances are typically separate - see DF-14/DF-15)

Note: Boundaries B6 and B7 previously covered the web Operator Console (SPA ↔ Control Plane and Console ↔ IAM); the web console has been removed. Operators now use the dvx CLI, whose operator authentication is covered by B5 (and the OAuth2 device flow against IAM). The remaining boundary numbers are unchanged.

Note: The database and Redis are shared backing stores reachable from multiple services (Control Plane, IAM, Broker, LLM Proxy) and are not drawn as boxes in the diagram above to keep it legible - see boundaries B9 (database) and B11 (Redis), and data flows DF-14/DF-15.

The dvxir sidecar shares the agent’s trust domain

On Kubernetes, an agent pod has two containers: the dvxir sidecar (bootstrap, JWT refresh, tunnel, SSH) and the operator’s unmodified main container. They are one trust domain, not two - there is no trust boundary between them, and none is relied upon. Identity is delivered to the main container via shared volumes: the 5-minute JWT read-only (a tampered JWT fails ES256 validation at the enclave/proxy, so the worst case is self-DoS, not impersonation), and the dvxir binary at /opt/dvx/bin/dvxir. The longer-lived refresh token stays on a sidecar-only volume the main container never mounts and out of argv/env, so its exposure is no greater than the in-container model it replaces. shareProcessNamespace lets the sidecar enter the agent (for SSH), one-directionally; the agent gains only read visibility of the sidecar’s process list, never its capabilities. Moving the runtime into a sibling container therefore crosses no boundary and grants the agent nothing new.

Control plane ↔ IAM tables

IAM owns the iam_* tables (accounts, organizations, RBAC roles, OAuth2 clients), and other services normally only validate IAM-issued JWTs via JWKS - they do not mutate IAM data. The operator IAM-management surface (/api/v1/iam/*, boundary B5) is the documented exception: it lets the control plane write to iam_* tables - creating accounts, mutating organizations, assigning/revoking roles, and managing OAuth2 clients - making the control plane a second writer alongside dvx iam. These writes are gated by RBAC (members:*, org:manage, clients:manage) and are strictly org-scoped from the operator JWT claims - no route accepts a client-supplied organization id, including for platform-admin - so the blast radius of a leaked operator token stays confined to its own tenant. The control plane and IAM share one database and trust domain, so this is a write path within an existing boundary, not a new one.


Data Flows

DF-1: Agent Discovery

  • Boundary: B1 (Agent → Enclave)
  • Trigger: Agent startup
  • Auth: None (unauthenticated)
  • Data: Agent sends GET /v1/agent/discover. Enclave returns capabilities: service name, version, available endpoints.

DF-2: Agent Bootstrap

  • Boundary: B1 (Agent → Enclave)
  • Trigger: Agent first boot with bootstrap token
  • Auth: Single-use bootstrap token in request body
  • Data: Agent sends { token }. Enclave returns identity bundle: { agent_id, organization_id, agent_name, jwt, refresh_token, secrets (decrypted), config, config_hash }.

DF-3: Agent JWT Renewal

  • Boundary: B1 (Agent → Enclave)
  • Trigger: Every 4-5 minutes via agent refresh loop
  • Auth: Refresh token in request body (single-use, family-rotated)
  • Data: Agent sends { refresh_token }. Enclave returns { jwt (new), refresh_token (new, rotated), secrets, config, config_hash }.

DF-4: Agent Secret Access

  • Boundary: B1 (Agent → Enclave)
  • Trigger: Agent requests a credential on demand
  • Auth: Platform JWT (Bearer token)
  • Data: Agent sends GET /v1/agent/secrets/{name}. Enclave returns { name, value (decrypted) }. Also: GET /v1/agent/secrets returns name list (no values).

DF-5: Agent Config Access

  • Boundary: B1 (Agent → Enclave)
  • Trigger: Agent startup, drift detection
  • Auth: Platform JWT (Bearer token)
  • Data: Agent sends GET /v1/agent/config. Enclave returns merged config (platform → org → agent). Agent can also write agent-scoped config via PUT /v1/agent/config/{key}.

DF-6: Agent LLM Request

  • Boundary: B2 (Agent → LLM Proxy), then B8 (Proxy → Upstream)
  • Trigger: Agent makes an inference call
  • Auth: Platform JWT (Bearer token) or x-api-key header
  • Data: Agent sends Anthropic Messages API request. Proxy validates JWT, resolves the agent’s LLM API key from the database, injects it, forwards to upstream. Buffers the SSE response, runs audit interceptors, replays to agent.

DF-7: LLM Audit Logging

  • Boundary: B9 (Proxy → Database)
  • Trigger: Every LLM request/response pair
  • Auth: Database connection credentials
  • Data: Proxy writes to llm_audit_log: organization_id, agent_id, model, input/output tokens, full request body (JSONB), full response body (JSONB).

DF-8: Enclave SSE Event Stream

  • Boundary: B3 (Enclave → Control Plane)
  • Trigger: Enclave startup (long-lived connection)
  • Auth: Enclave API key (Bearer token)
  • Data: Enclave connects to GET /v1/broker/events with optional Last-Event-ID cursor. Control plane streams SSE events (e.g., agent.created with agent_id, name, org_id). Heartbeat every 30 seconds.

DF-9: Agent Provisioning (Podman)

  • Boundary: B4 (Enclave → Podman)
  • Trigger: Enclave receives agent.created SSE event
  • Auth: Unix socket (implicit local trust)
  • Data: Enclave generates bootstrap token, calls Podman REST API to create container with env vars: DVX_BROKER_URL, DVX_BOOTSTRAP_TOKEN (plaintext), DVX_AGENT_ID, DVX_AGENT_NAME, DVX_TLS_CA. CA cert bind-mounted read-only.

DF-10: Agent Provisioning (Kubernetes)

  • Boundary: B4 (Enclave → Kubernetes API)
  • Trigger: Enclave receives agent.created SSE event
  • Auth: In-cluster service account token
  • Data: Enclave creates namespace (labeled, PSA Privileged), applies NetworkPolicy, waits for the merged CA ConfigMap (trust-manager: public roots + interception CA), and creates a two-container pod - the dvxir sidecar plus the operator’s main image - with runtimeClassName: kata-clh, so the pod runs inside its own kata VM. Kata is mandatory, not a per-broker opt-in: with no kata-clh RuntimeClass configured, no agent pod is created at all. The in-pod security context is deliberately loose for every agent (the image’s own default UID including root, image-default capabilities, Unconfined seccomp, allowPrivilegeEscalation: true, and a root dvxir sidecar that drops all capabilities and adds nine back - five for s6-overlay init plus four for nsenter SSH), while automountServiceAccountToken: false still denies Kubernetes API access. Platform identity inputs (bootstrap token, client cert) go to the sidecar only; operator-set per-agent env vars are injected into the main container. The kata VM boundary, not the pod security context, is what contains a container escape (see DF-10 Elevation of Privilege).

DF-11: Operator API (Agent CRUD)

  • Boundary: B5 (Operator → Control Plane)
  • Trigger: Operator action via the dvx CLI
  • Auth: IAM JWT (Bearer token, validated against JWKS)
  • Data: Create, list, get, update, delete agents. Change agent status. Revoke tokens. Trigger re-provisioning. All scoped by organization_id from JWT.

DF-12: Operator API (Enclave Management)

  • Boundary: B5 (Operator → Control Plane)
  • Trigger: Operator action
  • Auth: IAM JWT
  • Data: Register enclave (returns API key once), list, get, delete, rotate API key. All scoped by organization_id.

DF-13: Operator API (Secrets Management)

  • Boundary: B5 (Operator → Control Plane)
  • Trigger: Operator action
  • Auth: IAM JWT
  • Data: Create/update secret (plaintext in, encrypted at rest with AES-256-GCM). List secrets (metadata only - name, agent, timestamps - no encrypted values returned).

DF-14: Broker-Local JTI Blacklist (Kill Switch)

  • Boundary: B11 (Broker ↔ Broker-local Redis)
  • Trigger: Operator issues an isolate/restore action via the broker kill-switch RPC; also consulted on every platform JWT validation
  • Auth: None on the connection itself (local/in-network Redis, authenticated via requirepass/TLS if configured)
  • Data: The broker adds/removes the agent’s JTI under a dvx:jti: key with a TTL (internal/redis/blacklist.go). Isolation and restoration are performed entirely enclave-side - agent_runtime.isolated_at, refresh-token revocation, and the JTI blacklist all live in the broker’s own Redis, distinct from the control plane’s. Every subsequent JWT validation (agentauth.Validator configured WithBlacklist) checks this store before accepting a token.

DF-15: Control Plane / IAM Redis (Sessions, Device Flow, Rate Limiting)

  • Boundary: B11 (Control Plane/IAM ↔ Redis)
  • Trigger: SSO login/logout, OAuth2 device authorization polling, login rate limiting
  • Auth: None on the connection itself; Redis auth/TLS if configured
  • Data: IAM stores SSO session blobs (iam:session:) and a per-account session index (iam:account-sessions:) - the same key format is written by both the IAM OAuth service and the control-plane operator service’s account-suspension revocation path (design doc 036), so they must stay in lock-step. It also stores OAuth2 device codes (5-minute TTL) for the device authorization flow, and sliding-window login rate-limit counters (iam:ratelimit:) keyed by IP/account.

DF-16: Event Publishing and Purging

  • Boundary: Internal (Control Plane)
  • Trigger: Agent creation with broker_id (publish), hourly timer (purge)
  • Auth: None (in-process)
  • Data: Events written to broker_events table with auto-incrementing cursor. In-process event bus notifies SSE handler. Purge deletes events already consumed by their enclave (cursor-based) or owned by deleted enclaves.

DF-17: Telegram Integration

  • Boundary: B5 (Operator → Control Plane), then external (Control Plane → Telegram API)
  • Trigger: Operator links a Telegram bot to an agent
  • Auth: IAM JWT (operator), then Telegram bot token (validation call)
  • Data: Operator sends bot_token. Control plane validates via Telegram getMe API. Token encrypted and stored as agent secret (telegram:bot_token). Integration record created. Agent receives decrypted token during bootstrap/renewal.

DF-19: Broker URL Announce & Discovery

  • Boundary: B3 (Enclave → Control Plane) for the announce; B5 (Operator → Control Plane) for discovery
  • Trigger: Broker (enclave) startup advertises its operator-reachable URL; operator later resolves an agent via the dvx CLI
  • Auth: Broker JWT (POST /v1/broker/announce) for the announce; operator IAM JWT (agent GET) for discovery
  • Data: The broker reports its configured operator-reachable URL (--operator-broker-url, falling back to --agent-broker-url) to the control plane, which records it trust-on-first-use (TOFU) in brokers.url, keyed strictly by the authenticated broker_id from its JWT (never client-supplied). The operator URL is intentionally distinct from the agent-facing URL in split topologies (in-cluster agents reach the broker by Service DNS; external operators reach it via an ingress/tunnel). The control plane cannot auto-detect it (the broker dials out, often from behind NAT). The control plane returns this URL as broker_url on the agent GET/list responses so the dvx CLI can reach the correct broker for SSH/port-forward/tunnel without an explicit --broker-url. The CLI sends the operator’s IAM JWT to this broker URL to obtain a short-lived SSH certificate (and for terminal/tunnel), so the URL governs where an operator credential is sent. Mitigations: the URL is pinned on first announce and a later announce with a different URL is rejected and logged as a tamper signal (a stolen broker credential cannot silently repoint operators at an attacker host after the legitimate broker has connected once); an operator can re-pin authoritatively via PATCH /api/v1/brokers/{id}; and --broker-url always overrides the discovered value. TLS verification of the broker endpoint remains mandatory on the CLI.

STRIDE Analysis

DF-1: Agent Discovery

CategoryThreat
SpoofingAn attacker could stand up a fake enclave and respond to discovery requests, directing agents to malicious endpoints.
TamperingA MITM could alter the endpoint list in the response, redirecting agents to attacker-controlled services.
RepudiationDiscovery requests are unauthenticated and not logged - there is no record of which agents queried the enclave.
Information DisclosureThe response reveals enclave version and supported endpoints, which aids reconnaissance.
Denial of ServiceThe endpoint is unauthenticated and could be flooded.
Elevation of PrivilegeNot applicable - no authentication or authorization occurs.

DF-2: Agent Bootstrap

CategoryThreat
SpoofingAn attacker who intercepts or guesses a bootstrap token can impersonate the agent and claim its identity.
TamperingA MITM could alter the response, injecting a malicious JWT or modified secrets into the agent’s identity bundle.
RepudiationThe bootstrap event is logged, but the agent has no way to prove which identity bundle it received.
Information DisclosureThe response contains decrypted secrets in plaintext over the wire. If TLS is compromised or --insecure-skip-tls is used, all secrets are exposed.
Denial of ServiceAn attacker could consume the bootstrap token before the legitimate agent, permanently blocking it from bootstrapping. A flood of invalid tokens causes repeated database lookups.
Elevation of PrivilegePrevented by design. A bootstrap token for one agent cannot be used to claim a different agent’s identity - tokens are bound to a specific agent_id.

DF-3: Agent JWT Renewal

CategoryThreat
SpoofingMitigated. An attacker with a stolen refresh token can obtain a valid JWT and impersonate the agent, but this is bounded: refresh tokens are single-use and family-rotated, so the theft window closes as soon as either party (attacker or legitimate agent) next renews - see the Denial of Service row below for what happens on that collision.
TamperingA MITM could modify the renewed JWT or secrets in transit if TLS is compromised.
RepudiationRenewal events are logged with agent_id. Replay detection creates a forensic trail when tokens are reused.
Information DisclosureSame as bootstrap - decrypted secrets are delivered in the response body.
Denial of ServiceAn attacker who steals a refresh token can use it first, which triggers replay detection and revokes the entire token family - denying service to the legitimate agent.
Elevation of PrivilegePrevented by design. Refresh tokens are scoped to a single agent - they cannot be used to renew tokens for a different agent.

DF-4: Agent Secret Access

CategoryThreat
SpoofingAn attacker with a stolen JWT can retrieve secrets for that agent.
TamperingNot applicable - endpoint is read-only.
RepudiationSecret access is not individually logged (only renewal/bootstrap delivery is audited).
Information DisclosureSecrets are returned in plaintext. A stolen JWT grants access to all of the agent’s secrets for the JWT’s lifetime. The secret name list endpoint reveals what integrations an agent has.
Denial of ServiceRepeated requests could cause load on decryption operations.
Elevation of PrivilegePrevented by design. An agent can only access its own secrets - the enclave resolves agent_id from the JWT claims, not from the request.

DF-5: Agent Config Access

CategoryThreat
SpoofingAn attacker with a stolen JWT can read and write agent-scoped config.
TamperingA compromised agent can write arbitrary values to its own config, potentially influencing platform behavior (e.g., downstream URL overrides).
RepudiationConfig writes are not individually audited.
Information DisclosureConfig values (including org-level overrides) are returned in plaintext. May reveal infrastructure details (URLs, feature toggles).
Denial of ServiceAn agent could write excessive config entries.
Elevation of PrivilegeAgents can only write agent-scoped config - they cannot modify org-level or platform-level config. However, if config merging gives agent-level config precedence, a compromised agent could override org-level settings for itself.

DF-6: Agent LLM Request

CategoryThreat
SpoofingAn attacker with a stolen JWT can make LLM calls as the agent, consuming API credits and potentially exfiltrating data via crafted prompts.
TamperingA compromised agent can send any content to the LLM - there is no request content validation at the proxy layer.
RepudiationFull request and response bodies are written to the audit log, providing non-repudiation for LLM interactions.
Information DisclosureThe proxy resolves and injects the LLM API key - a compromised proxy or database attacker could intercept it. The agent never sees the raw API key (it’s injected after JWT validation).
Denial of ServiceNo rate limiting on the proxy - a compromised agent can make unlimited LLM calls, exhausting API credits. Request body limit is 32 MB.
Elevation of PrivilegeThe proxy falls back from agent-level to org-level secrets for API key resolution - all agents in an org share the org-level LLM credential.

DF-7: LLM Audit Logging

CategoryThreat
SpoofingNot applicable - audit writes are internal (proxy to database).
TamperingIf the database is compromised, audit records could be modified or deleted. The table is append-only by convention but not enforced at the database level.
RepudiationThe audit log exists to prevent repudiation. If it can be tampered with, repudiation becomes possible.
Information DisclosureThe audit log stores complete request and response bodies as JSONB. This may contain sensitive data from LLM interactions (PII, credentials in prompts, proprietary content). Anyone with database read access can see all logged conversations.
Denial of ServiceThe audit log grows continuously with no built-in retention. Over time it could consume all available storage. Logging is fire-and-forget - audit failure does not block the request.
Elevation of PrivilegeNot applicable - audit writes use the proxy’s database credentials.

DF-8: Enclave SSE Event Stream

CategoryThreat
SpoofingAn attacker with a stolen enclave API key can connect to the SSE stream and receive events intended for that enclave.
TamperingEvents are written by the control plane and delivered read-only via SSE - a network attacker cannot inject events into the stream. However, a database attacker could insert fake events directly into the broker_events table.
RepudiationEvent consumption is tracked via cursor (last_event_id) but individual event reads are not logged.
Information DisclosureEvents contain agent_id, agent_name, and organization_id. A stolen API key reveals all agents assigned to that enclave.
Denial of ServiceA rogue client holding the API key could maintain a persistent connection, potentially blocking the legitimate enclave from connecting (depending on server-side connection limits). Events accumulate if the enclave never processes them.
Elevation of PrivilegeEvents are scoped to the authenticated broker_id - an enclave cannot receive events for other enclaves.

DF-9: Agent Provisioning (Podman)

CategoryThreat
SpoofingAn attacker with access to the Podman Unix socket can create containers that appear to be legitimate agents.
TamperingThe bootstrap token is passed as a plaintext environment variable - any process that can inspect container metadata or /proc on the host can read it. The container image could be tampered with if the registry is compromised.
RepudiationContainer creation is not audited at the platform level (Podman’s own logs may capture it).
Information DisclosureBootstrap tokens and TLS CA paths are visible in container environment variables. No network isolation - containers on the same host can communicate with each other and reach host services.
Denial of ServiceNo resource limits are applied to containers by default. A single agent could exhaust host CPU or memory.
Elevation of PrivilegeContainers share the host network namespace (pasta networking). A compromised agent can reach other agents, the database, Redis, and any host-local service.

DF-10: Agent Provisioning (Kubernetes)

CategoryThreat
SpoofingAn attacker with access to the Kubernetes API and the enclave’s service account could create pods that impersonate agents.
TamperingThe bootstrap token is passed as a plaintext environment variable in the pod spec. Anyone with get pods permission in the namespace can read it.
RepudiationKubernetes audit logging (if enabled) records pod creation.
Information DisclosureBootstrap tokens in pod env vars. NetworkPolicy blocks private ranges but not link-local addresses (169.254.0.0/16) - cloud metadata services may be reachable.
Denial of ServiceNo resource limits are set on agent pods by default - a single agent could exhaust node resources. An attacker who can trigger agent creation could exhaust namespace or pod quotas.
Elevation of PrivilegeEvery agent pod runs in its own kata-containers VM (kata-clh) - mandatory for all agents, not an opt-in - so a container escape is bounded to that one agent’s VM rather than the node. That VM is the only containment boundary: the in-pod context is deliberately loose (image-default/root UID, image-default capabilities, Unconfined seccomp, allowPrivilegeEscalation: true, plus a root dvxir sidecar holding CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_SYS_CHROOT, and CAP_AUDIT_WRITE for nsenter SSH). There is no in-pod hardening layer behind it, and the loosening is not free even for the boundary we do rely on: capabilities and seccomp also bound the guest-kernel syscall surface that a guest-to-VMM escape would chain through, so Unconfined everywhere maximizes exposure to that single boundary. automountServiceAccountToken: false still blocks the Kubernetes API from every container in the pod. Agent namespaces run PSA Privileged fleet-wide, which knowingly gives up Baseline’s admission-level blocks on hostPath, hostNetwork, and hostPID. Kata does not subsume those: a hostPath is bridged into the guest over virtiofs, so a host mount crosses the VM boundary by design - a provisioning-spec regression that added one would expose host files to the agent despite kata. (Host namespace sharing is at best degraded under a VM runtime, but nothing now rejects a spec that asks for hostNetwork/hostPID.) Today the only thing preventing that is that the pod spec is entirely backend-controlled (no operator input); the compensating control named by ADR 046, and not yet implemented, is a narrow validating admission policy forbidding host mounts and host namespaces on agent namespaces.

DF-11: Operator API (Agent CRUD)

CategoryThreat
SpoofingAn attacker with a stolen or forged IAM JWT can perform operations as that operator.
TamperingAn authenticated operator can modify any agent in their organization - there is no role-based restriction on who can delete, suspend, or revoke.
RepudiationOperations are not individually audit-logged at the application level (only database-level timestamps on mutations).
Information DisclosureAgent list reveals all agents, their status, enclave assignments, and last-heard-from timestamps for the organization.
Denial of ServiceNo rate limiting - an authenticated operator could mass-create or mass-delete agents. List endpoints return all records without pagination.
Elevation of PrivilegeOpen - no owner tracked here. JWT claims include Roles and Permissions fields, but no handler checks them - any authenticated user in the organization has full access to all operations (create, delete, suspend, revoke). Queries are scoped by organization_id from the JWT, preventing cross-tenant access, but there is no intra-org restriction at all. Same gap repeated in DF-12 and DF-13.

DF-12: Operator API (Enclave Management)

CategoryThreat
SpoofingSame as DF-11 - stolen IAM JWT.
TamperingAn operator can delete an enclave that has active agents assigned to it, orphaning those agents (no pre-deletion check). Token rotation invalidates the previous key immediately.
RepudiationEnclave creation and token rotation return plaintext keys - no audit record of who received the key.
Information DisclosureEnclave API key is returned in plaintext once. If the response is logged or intercepted, the key is compromised. Enclave list reveals infrastructure topology (names, status, last_seen_at).
Denial of ServiceDeleting an enclave orphans all its agents. Rotating a key immediately disconnects the enclave from SSE.
Elevation of PrivilegeOpen - same gap as DF-11. Any org member can manage enclaves, including deleting one out from under active agents.

DF-13: Operator API (Secrets Management)

CategoryThreat
SpoofingSame as DF-11 - stolen IAM JWT.
TamperingAn authenticated operator can overwrite any agent’s secrets. There is no confirmation step or version control on secret values.
RepudiationSecret writes update timestamps but do not log who wrote the value or what the previous value was.
Information DisclosureSecret values are accepted in plaintext over the wire (HTTPS), encrypted before storage. The list endpoint returns metadata only (no values). However, the copyable field reveals whether a secret’s plaintext was retained for display.
Denial of ServiceNo limit on the number or size of secrets per agent.
Elevation of PrivilegeOpen - same gap as DF-11. Any org member can create or overwrite secrets for any agent in the org.

DF-14: Broker-Local JTI Blacklist (Kill Switch)

CategoryThreat
SpoofingNot applicable - the broker connects to its own Redis with a fixed connection string; there is no per-request identity to spoof.
TamperingAn attacker with write access to the broker’s Redis could delete a blacklisted JTI, silently un-revoking an isolated agent’s still-valid JWT for the remainder of its ≤5-minute lifetime.
RepudiationThe blacklist add/remove itself is not independently logged; only the control-plane audit_log row for the operator’s isolate/restore action is recorded (internal/operator/service_killswitch.go) - there is no enclave-side record of the Redis mutation succeeding.
Information DisclosureJTIs are opaque random identifiers; blacklist membership discloses only that a specific token was revoked, not any secret or claim content.
Denial of ServiceOpen. If blacklist checking is required (WithBlacklist) and Redis becomes unreachable, JWT validation fails closed for every agent on that broker (ErrBlacklistRequired/check error) - a Redis outage denies service platform-wide, not just to isolated agents. Conversely, if a deployment opts out (WithoutBlacklist), an isolated agent’s JWT keeps working until it naturally expires (≤5 min), so isolation is not truly instant without Redis.
Elevation of PrivilegeNot applicable - the blacklist can only revoke a token, never mint or upgrade one.

DF-15: Control Plane / IAM Redis (Sessions, Device Flow, Rate Limiting)

CategoryThreat
SpoofingAn attacker with Redis write access could forge a session blob or device-code entry and potentially authenticate as an arbitrary account without going through login.
TamperingOpen. Session storage and account-wide revocation are written by two separate codebases (IAM’s OAuth service and the control plane’s operator service) that must agree on the exact same key format by convention (design doc 036) with no shared runtime check - a key-format drift introduced in either writer would silently break account-session revocation (a suspended account’s sessions would not actually revoke).
RepudiationSession and device-code reads/writes are not individually logged.
Information DisclosureSession and device-code values are effectively bearer credentials; an attacker with Redis read access could hijack an active SSO session or complete a pending device authorization before the legitimate user does.
Denial of ServiceMitigated, favoring availability. The login rate limiter fails open on a Redis error (return true, err // Fail open - allow request if Redis is down in internal/iam/ratelimit.go) - a Redis outage silently disables login rate limiting rather than blocking logins, which is a deliberate availability-over-strictness tradeoff worth confirming is still the intended default.
Elevation of PrivilegeNot applicable - this store holds session/rate-limit state, not authorization decisions.

DF-16: Event Publishing and Purging

CategoryThreat
SpoofingEvents are created by the control plane’s service layer - no external input. A database attacker could insert fake events.
TamperingA database attacker could modify event payloads (agent_id, org_id) or manipulate the enclave’s cursor to cause events to be replayed or skipped.
RepudiationEvents are append-only by convention. No integrity protection (signing, HMAC) on event payloads.
Information DisclosureEvents contain agent_id, agent_name, and organization_id - visible to anyone with database read access.
Denial of ServiceEvents accumulate if enclaves don’t process them. The purge job only removes consumed events. A disconnected enclave’s events grow without bound.
Elevation of PrivilegeFake events could trigger provisioning of containers - but the provisioner validates agent state (must be “created” or “provisioning”) before generating a bootstrap token.

DF-17: Telegram Integration

CategoryThreat
SpoofingAn attacker with a valid Telegram bot token could register it before the legitimate operator, hijacking the bot’s identity. The getMe validation only checks that the token is valid, not that the operator owns the bot.
TamperingThe bot token is encrypted at rest but delivered in plaintext to the agent during bootstrap/renewal. A compromised agent has full access to the Telegram bot.
RepudiationIntegration creation is recorded in the database with timestamps but no audit trail of who created it.
Information DisclosureThe Telegram bot token grants full control of the bot (read messages, send messages, access chat members). This is a high-value credential delivered alongside all other secrets.
Denial of ServiceDeleting the integration deletes the encrypted secret - the agent loses access on next renewal.
Elevation of PrivilegeThe Telegram bot token is scoped to a single bot - it cannot be used to access other bots or the operator’s Telegram account.

DF-18: Execution-Time Policy Evaluation

  • Boundary: B2 (Agent → LLM Proxy)
  • Trigger: Agent’s LLM response contains tool_use blocks
  • Auth: Platform JWT or netproxy-forwarded service-signed identity headers
  • Data: LLM proxy inspects tool calls against execution-time policies (3-layer architecture: inspectors → policy engine → enforcers). Policy decisions are logged to policy_decisions. Actions requiring approval enter the approval workflow; denied actions are returned as tool_result(is_error).
CategoryThreat
SpoofingTool name spoofing: A compromised agent could rename tools to evade LLM-level policies (e.g., rename stripe.create_refund to process_payment). LLM-level policies match on agent-defined names, so this is a real risk. Mitigations: network-level policies as backstop (destination matching doesn’t depend on tool names), tool catalog with “expected tools” alerting, and tool definition change detection between requests. Approval spoofing: Approval resolution requires an authenticated IAM session; the resolved_by field is set from the caller’s verified identity. LLM Judge resolvers authenticate via platform service credentials with resolved_by set to the judge’s config ID.
TamperingPolicy version drift: If a policy is updated while an approval is pending, the wrong version could apply. Mitigation: policy decisions are logged with a snapshot of the policy at evaluation time (policy_version field). TOCTOU between evaluation and execution: The proxy evaluates tool_use blocks from the LLM response, but the agent may modify parameters before actual execution. LLM-layer policies constrain the LLM’s output; network-layer policies are the binding enforcement point. For high-risk operations, operators should define policies at both layers.
RepudiationAll policy decisions (allow, deny, require_approval) are logged to policy_decisions with the full evaluation context. Approval resolutions record resolved_by, resolver_type, and timestamp.
Information DisclosureDenial messages default to a generic “action denied by policy” with no threshold values, policy names, or rule details exposed. Operators can opt into custom denial_message fields when they want to help well-behaved agents self-correct. LLM Judge resolver manipulation: A compromised agent could craft tool inputs or conversation content to manipulate an LLM Judge resolver into auto-approving a dangerous action (prompt injection via request_context). Mitigations: request_context sanitization with structured envelopes and clear delimiters, LLM Judge resolvers use dedicated service accounts, operators can disable LLM Judge tiers for high-risk policies via "human_only": true, and all auto-approvals are logged with resolver_type: "llm_judge" for separate audit review.
Denial of ServiceApproval timeout flooding: An attacker could flood the approval queue to overwhelm human reviewers, causing legitimate requests to time out or exhaust escalation tiers. Mitigations: max pending approval requests per agent (default: 10), org-wide pending approval limit (default: 100), per-agent rate limit on approval request creation (max 50/hour). When any limit is exceeded, require_approval verdicts become deny with reason “approval rate limit exceeded.”
Elevation of PrivilegePolicies are stored in the database and only modifiable by authenticated operators with org:manage permission. Agents cannot read or modify their own policies through the enclave API - the enclave only delivers the action result, never the policy definition.

DF-19: Broker URL Announce & Discovery

CategoryThreat
SpoofingA broker can only announce its own URL: the record is keyed by the broker_id in the authenticated broker JWT, never a client-supplied id, so one broker cannot set another’s URL.
TamperingThe announced URL is recorded trust-on-first-use and is immutable via announce thereafter - a second announce with a different URL is ignored and logged. Repointing requires either the authoritative operator PATCH /api/v1/brokers/{id} (RBAC org:manage, org-scoped) or direct database access. The residual window is between broker registration and the first legitimate announce (classic TOFU race); a mismatch alert fires when the real broker later announces a different URL.
RepudiationFirst-announce pins and ignored-mismatch announces are logged with broker_id and the URLs involved.
Information DisclosureThe broker_url is returned only on org-scoped operator agent reads (B5); it reveals the broker’s network address to operators already entitled to use it.
Denial of ServiceA broker that never announces simply leaves broker_url empty; the CLI then falls back to requiring --broker-url, so a missing/failed announce degrades to the prior behaviour rather than blocking operations.
Elevation of PrivilegeThe discovered URL is where the CLI sends the operator’s IAM JWT for SSH-cert/terminal/tunnel, so a compromised broker that poisons its own URL before first legitimate announce could harvest an operator credential broader than its enclave. Mitigations: TOFU pin + mismatch logging, authoritative operator PATCH override, the always-available --broker-url flag, and mandatory TLS verification of the broker endpoint.