
# CIDR-Bound JWTs (IP Binding)

> **A platform capability Daevix operates.** IP binding is configured on the **enclave**, which Daevix runs for you today - so the flags shown below are enclave-side settings, not things you set through the `dvx` CLI in Stage 1. This page explains the guarantee and how it works; the `dvx serve broker` commands apply when you operate the enclave yourself. (On the command line the enclave is still the `broker` component.)

## Problem

Agent platform JWTs are pure bearer tokens - anyone with a valid, unexpired JWT can use it from any network location. If a token is exfiltrated (e.g., via a compromised agent, log leak, or insecure storage), the attacker can impersonate the agent from anywhere on the internet.

## Solution

CIDR-bound JWTs. The enclave embeds a `client_cidr` claim in the agent's platform JWT, binding the token to the network the agent is on. Every service that validates a platform JWT checks the request's source IP against the bound CIDR. A token used from outside the bound CIDR is rejected with a **403 Forbidden**.

## How It Works

### Token Minting Flow

When an agent bootstraps or renews its JWT, the enclave:

1. Extracts the agent's source IP (via `RemoteAddr` or `X-Forwarded-For` with trusted proxy traversal)
2. Matches the IP against the configured CIDRs using **longest-prefix match** (most specific CIDR wins)
3. Embeds the matched CIDR as the `client_cidr` claim in the JWT
4. If no configured CIDR matches, falls back to exact IP binding (`/32` for IPv4, `/128` for IPv6)

### Token Validation Flow

All HTTP-context platform JWT validation goes through `agentauth.Validator.ValidateRequest(r)`:

1. Extract Bearer token from the `Authorization` header
2. Validate JWT signature, expiry, issuer, audience, scope
3. If `client_cidr` claim is present, extract the request's client IP
4. Check that the client IP falls within the bound CIDR
5. If the IP is outside the bound CIDR, return `ErrCIDRMismatch` (HTTP 403)

### Opt-In / Opt-Out

The feature is fully opt-in:

- **No `--ip-bind-cidrs` configured** = no `client_cidr` claim minted = no enforcement anywhere
- **`--ip-bind-cidrs` configured** = all minted tokens get a `client_cidr` claim = enforcement at every validator

Existing tokens without a `client_cidr` claim continue to work normally (no enforcement for those tokens).

## Configuration

### Enclave flags (`dvx serve broker`)

| Flag | Env Var | Description |
|------|---------|-------------|
| `--ip-bind-cidrs` | `DVX_IP_BIND_CIDRS` | Comma-separated CIDR ranges for IP-bound agent JWTs |
| `--trusted-proxies` | `DVX_TRUSTED_PROXIES` | Comma-separated trusted reverse proxy CIDRs |

### Proxy Flags

| Flag | Env Var | Description |
|------|---------|-------------|
| `--trusted-proxies` | `DVX_TRUSTED_PROXIES` | Comma-separated trusted reverse proxy CIDRs |

The proxy does not need `--ip-bind-cidrs` because it only validates tokens - the enclave mints them.

### Example

```bash
# Enclave: bind agents to the 10.0.1.0/24 and 10.0.2.0/24 subnets
dvx serve broker --ip-bind-cidrs 10.0.1.0/24,10.0.2.0/24

# Behind a load balancer: trust proxy CIDRs for XFF extraction
dvx serve broker --ip-bind-cidrs 10.0.0.0/8 --trusted-proxies 10.0.0.1/32
dvx serve llmproxy --trusted-proxies 10.0.0.1/32
```

## CIDR Matching

When multiple configured CIDRs overlap, the **longest prefix** (most specific) wins:

```
--ip-bind-cidrs 10.0.0.0/8,10.0.1.0/24
```

An agent at `10.0.1.5` gets bound to `10.0.1.0/24` (more specific), not `10.0.0.0/8`.

## Fallback Behavior

If the agent's source IP doesn't match any configured CIDR, the enclave binds the token to the agent's exact IP:

- IPv4: `/32` (e.g., `203.0.113.50/32`)
- IPv6: `/128` (e.g., `2001:db8::1/128`)

This ensures every agent gets IP binding when the feature is enabled, even if the operator's CIDR list is incomplete.

## X-Forwarded-For and Trusted Proxies

When enclaves or proxies sit behind load balancers, the agent's real IP is in the `X-Forwarded-For` header, not `RemoteAddr`.

### Algorithm (RFC 7239 Right-to-Left Traversal)

1. Build the full IP chain: all `X-Forwarded-For` entries (left-to-right) + `RemoteAddr`
2. Walk the chain **right-to-left**
3. Skip any IP that falls within a trusted proxy CIDR
4. The first non-trusted IP is the real client IP
5. If all IPs are trusted (misconfiguration), fall back to `RemoteAddr`

### Security Implications

| Scenario | Behavior |
|----------|----------|
| **No `--trusted-proxies`** | XFF is ignored entirely. Only `RemoteAddr` is used. Safe default. |
| **Trusted proxies too broad** | An attacker could spoof XFF to inject an IP that passes CIDR checks. Configure trusted proxies narrowly. |
| **All IPs trusted** | Falls back to `RemoteAddr`, which is set by the network stack and cannot be spoofed by the client. |

## Limitations

- **On-host compromise.** CIDR binding protects against off-network token use, not on-host compromise. An attacker on the same network segment can still use a stolen token. CIDR binding is one layer; it composes with the agent's short-lived [identity](/security-model/#agent-identity) and the network/host controls described in the [Security Model](/security-model/).
- **NAT**: If agents are behind NAT, they share a source IP. CIDR binding still works (the NAT IP falls within the configured CIDR) but doesn't distinguish individual agents.
- **Dynamic IPs**: If agent IPs change between renewal cycles, they may get different CIDRs bound. The token is short-lived (5 minutes), so this is rarely an issue.
- **No database changes**: The CIDR is a JWT claim, not a database column. No schema migrations needed. The CIDR is transient - determined at minting time from the agent's source IP and the enclave's config.
