
# Security Model

## Core Posture: Never Trust the Agent

The platform treats the agent process as **untrusted**. This is not a pessimistic assumption - it is the only correct assumption, because:

1. **The agent might not be ours** - Operators can run any agent code (BYOA). The platform cannot guarantee what's running inside the container.
2. **The agent could modify itself** - Depending on its access level, an agent may alter its own binary, configuration, or behavior. Even without admin rights, it may find ways to act outside its intended scope.
3. **The agent acts autonomously** - It makes decisions that are not fully predictable by the operator.
4. **LLMs can be manipulated** - Prompt injection, jailbreaking, and adversarial inputs are real risks that can cause agents to take unintended actions.

Therefore, all security controls exist **outside** the agent process, at layers the agent cannot subvert (or at least, where subversion is detectable).

## Principles

### Authenticate at Every Boundary

Every component-to-component interaction requires authentication:

| Flow | Mechanism |
|------|-----------|
| Agent → Enclave | Platform JWT (ES256, 5-minute lifetime) |
| Agent → LLM proxy | Netproxy-forwarded identity (service-signed headers) or platform JWT - see [Agent identity](#agent-identity) |
| Enclave → Control Plane | API key (SHA-256 hashed) |
| Operator (`dvx` CLI) → Control Plane API | Operator JWT (IAM-issued) |

No component accepts unauthenticated requests for state-changing operations. The only unauthenticated endpoint is `GET /v1/agent/discover` (read-only enclave capabilities).

### Defense in Depth

No single control is sufficient. Daevix layers network controls, host controls, platform controls, and (optionally) agent-level controls. A failure at one layer should be caught by the next.

### Least Privilege

- Agents receive only the credentials they need (scoped secrets, short-lived JWTs).
- Kubernetes agents run as non-root (UID 1000) with `automountServiceAccountToken: false`.
- NetworkPolicy restricts agent egress to DNS, the enclave, the proxy, and the public internet - no east-west traffic to private services.
- The enclave cannot run database migrations. Only the control plane can.

### Secrets Never at Rest in Plaintext

- All tokens (bootstrap, refresh, enclave API keys) are stored as **SHA-256 hex digests**. The database never sees raw tokens.
- Agent secrets are encrypted with **AES-256-GCM** (random 12-byte nonce, prepended to ciphertext). Decrypted only during delivery to the agent.

### Fail Closed

- Missing or invalid authentication → 401.
- CIDR mismatch → 403.
- Token replay → revoke the entire token family + 409.
- Unknown errors → generic 500 to the client, real error logged server-side.
- No fallback to insecure modes in production.

## Four Control Layers

Daevix uses a defense-in-depth model with four layers of security controls, each enforced at a level the agent cannot easily bypass.

### Layer 1: Network Controls

Network controls restrict what the agent can reach. These are enforced by the organization's network infrastructure - firewalls, load balancers, and cloud security groups.

| Control | Description |
|---------|-------------|
| **Egress filtering** | Agents can only reach services they need. A K8s deployment agent doesn't need access to the email server. |
| **TLS inspection** | Organizations can use TLS-intercepting proxies to inspect agent traffic, using corporate CA certificates baked into the base image. |
| **DNS filtering** | Block access to known-bad domains, restrict to approved services. |
| **Network segmentation** | Agent hosts placed in a dedicated network segment with its own rules, separate from production and employee workstations. |

In Kubernetes deployments, Daevix enforces network isolation per agent via **NetworkPolicy**. Each agent namespace gets a policy (`agent-isolation`) that:
- Denies all ingress
- Allows egress to DNS (port 53)
- Allows egress to the enclave (ports 8444/8445)
- Allows egress to the internet (excluding private ranges `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`)

### Layer 2: Host Controls

Agent hosts should have the same security controls as any managed endpoint in the organization.

| Control | Description |
|---------|-------------|
| **EDR** | CrowdStrike, SentinelOne, Wazuh, etc. Detects malicious behavior, provides response capabilities. |
| **Audit logging** | `auditd` capturing syscalls, process execution, file access, network connections. Shipped to SIEM. |
| **File integrity monitoring** | Detect unexpected changes to critical files (including the agent binary itself). |
| **Mandatory access controls** | SELinux or AppArmor profiles to limit what the agent process can do, even with root access. |
| **Immutable infrastructure** | Agents can run on read-only root filesystems with writable scratch space, making persistent compromise harder. |

In Kubernetes, agent pods run with a restricted security context:
- `runAsNonRoot: true`
- UID/GID 1000
- `automountServiceAccountToken: false`
- `fsGroup: 1000`

### Layer 3: Platform Controls

Controls provided natively by Daevix at the platform level.

| Control | Description |
|---------|-------------|
| **Identity revocation** | Instantly revoke all agent credentials, rendering it unable to authenticate to any service. |
| **Credential scoping** | External identities scoped to minimum required permissions. An agent gets the IAM role it needs, not admin. |
| **Audit trail** | Complete record of agent creation, bootstrap, credential issuance, rotation, and revocation. |
| **Token hashing** | All tokens (bootstrap, refresh) stored as SHA-256 digests. The database never sees raw tokens. |
| **Secrets encryption** | Agent secrets encrypted at rest with AES-256-GCM. Decrypted only during bootstrap/renewal. |
| **CIDR-bound JWTs** | Optional IP binding that prevents token use from outside the agent's network. See [IP Binding](/ip-binding/). |
| **Short-lived agent identity** | Agents authenticate with enclave-issued platform JWTs (ES256, 5-minute lifetime); agents reaching the LLM proxy through the netproxy are identified by service-signed headers. See [Agent identity](#agent-identity). |
| **Refresh token families** | Family-based rotation with replay detection. Reusing an old refresh token revokes the entire family. |
| **JTI blacklisting** | Optional Redis-backed instant JWT revocation. Without Redis, revocation takes up to 5 minutes (token expiry). |
| **LLM content inspection** | Content security pipeline inspects all LLM traffic for PII, API key leaks, secret exposure, and custom patterns. Policies cascade Platform > Org > Agent with immutable floors. See [LLM Content Policy](/content-policy/). |
| **Model restriction** | Allowlist or blocklist which LLM models agents can use. Enforced at the proxy before the request reaches the upstream provider. |
| **Execution-time policy engine** | Three-layer architecture (inspectors → policy engine → enforcers) evaluates tool calls from LLM responses against operator-defined policies. Supports `allow`, `deny`, `alert`, and `require_approval` actions. Both LLM proxy and network proxy share the same inspection library and policy engine. |
| **Approval workflow** | Tool calls matching `require_approval` policies enter an escalation-tier workflow. Tiers progress from LLM Judge auto-resolution to human approval by an operator (`dvx policy approvals`). Per-agent and org-wide limits prevent approval queue flooding. Approvals are scoped via canonicalized tool call hashing. |
| **Fail-closed policy cache** | When the execution-time policy cache is cold or stale and the database is unreachable, all actions are denied by default. Operators can configure fail-open as an org-wide setting (requires `org:manage` permission and compensating controls acknowledgment). Every fail-open event is logged at WARN level. |
| **Kill switch** | The enclave can terminate agent compute immediately, without waiting for graceful shutdown (`dvx agent isolate`). |

### Layer 4: Agent-Level Controls (Platform Runtime Only)

If the agent uses the platform-provided runtime, additional controls are available. These are explicitly **best-effort** - they run inside the agent's trust boundary and could be bypassed by a modified agent.

| Control | Description |
|---------|-------------|
| **Tool call inspection** | All tool calls logged with arguments and results. Operators can set alert policies. |
| **LLM API proxy** | Route LLM calls through the platform proxy for logging and spend control enforcement. |
| **Human-in-the-loop gates** | Certain tool calls can require operator approval before execution. |
| **Behavioral policies** | Rules like "never commit directly to main" enforced at the tool level. |

## Token Security

### Platform JWTs

- Algorithm: ES256 (ECDSA P-256) - algorithm confusion attacks are rejected
- Lifetime: 5 minutes
- Subject: `agent:<id>`
- Issued by the enclave, not the control plane
- Optionally CIDR-bound to the agent's network

### Agent identity

An agent's identity is its short-lived **platform JWT**, issued by the enclave at bootstrap and rotated continuously (ES256, 5-minute lifetime). There are two paths by which an agent's identity reaches the LLM proxy:

1. **Netproxy-forwarded identity.** When agent traffic flows through the network proxy (the default for managed agents), the netproxy attaches a service-signed `X-Dvx-Service-Authorization` header alongside `X-Dvx-*` identity headers. The LLM proxy verifies the service signature cryptographically and trusts the identity it carries. This is the path the default Claude Code agent uses, and it's how an agent can forward its *own* upstream LLM credentials while the platform still tracks who it is.
2. **Platform JWT (direct).** Agents that talk to the LLM proxy directly present their enclave-issued platform JWT via `Authorization: Bearer …` or the Anthropic-style `x-api-key` header.

> **A note on mTLS.** Earlier versions identified agents to the LLM proxy with short-lived mTLS client certificates. That path was removed - long-lived agent connections don't re-read a rotating client certificate, so 5-minute certs broke mid-session, and carrying a third auth path added complexity for no benefit. Service-to-service mTLS between platform components (enclave, control plane, relay) is unaffected.

### Bootstrap Tokens

- Random 32 bytes
- SHA-256 hashed before storage
- 1-hour expiry
- Single-use - burned on first exchange
- Plaintext returned once on creation, never stored

### Refresh Tokens

- 24-hour lifetime
- Single-use with family-based rotation
- SHA-256 hashed before storage
- Replay detection: reusing an old token from the same family revokes the entire family
- Used to obtain new platform JWT + new refresh token

## Platform Security Guarantees

These properties are built into the codebase. Operators benefit from them without additional configuration.

### JWT Algorithm Restriction

All JWT validators enforce **ES256 only**. Algorithm confusion attacks (e.g., accepting `none` or `HS256` with a public key as the signing secret) are rejected at the library level. Both operator JWTs and platform JWTs go through the same strict validation: signature verification, issuer, audience, and expiration checks.

Platform JWT validation in HTTP contexts additionally enforces CIDR binding when the `client_cidr` claim is present (see [IP Binding](/ip-binding/)).

### Tenant Isolation

Every API query is scoped by `organization_id` extracted from the caller's JWT. The platform never trusts organization identity from request bodies - it always uses the cryptographically verified value from the token. There is no API path that can return data across organizations.

This holds for IAM administration as well. The control plane exposes an operator-facing IAM-management surface (`/api/v1/iam/*`) that can mutate IAM data - accounts, organizations, RBAC role assignments, and OAuth2 clients - making it a **second writer** to the IAM tables (which `dvx iam` otherwise owns). Every such operation is gated by RBAC (`members:*`, `org:manage`, `clients:manage`) and is **strictly org-scoped from the operator JWT claims** - no route accepts a client-supplied organization id, including for `platform-admin`. A leaked operator token can therefore only ever touch its own tenant's IAM data. The control plane and IAM already share one database and trust domain, so this write boundary stays inside an existing trust domain rather than crossing a new one.

### SQL Injection Prevention

All database queries are generated by [sqlc](https://sqlc.dev/) as parameterized statements with type-safe Go bindings. No SQL is constructed by string concatenation anywhere in the codebase.

### Error Masking

All API error responses use [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457) (`application/problem+json`). Internal details - stack traces, SQL errors, file paths - are never exposed to clients. Errors are logged server-side with full context for debugging, while clients receive sanitized messages.

### Cryptographic Randomness

All security-sensitive random values (tokens, nonces, CSRF tokens) are generated using cryptographically secure sources (`crypto/rand` in Go, `crypto.getRandomValues()` in the browser).

### Logging Hygiene

The platform uses structured logging and enforces a strict boundary: security-relevant events (authentication outcomes, status transitions, token lifecycle) are logged, while sensitive values (raw tokens, plaintext secrets, database credentials, private keys) are never written to logs.

### Audit Trail Integrity

Daevix uses a tombstone pattern (soft deletes) for data deletion - no security-relevant data is hard-deleted. Tables have a `deleted_at` column, and all read queries filter on active records. This preserves a complete audit trail of agent creation, credential issuance, and lifecycle transitions.
