Model gateway

Model API keys never enter the workspace.

model-gateway accepts its own client token from the workspace, strips it, injects the real provider key, and forwards only allowlisted paths. Compose sets ANTHROPIC_BASE_URL=http://model-gateway:7434/anthropic and exports ANTHROPIC_AUTH_TOKEN from the gateway token file — Claude Code and the Anthropic SDKs work unchanged.

Note

Use ANTHROPIC_AUTH_TOKEN (sent as Authorization: Bearer). Don't set ANTHROPIC_API_KEY in the workspace — it's sent as x-api-key, which the gateway doesn't accept as a client token.

Configure routes

{
  "routes": [
    {
      "prefix": "/anthropic",
      "upstream": "https://api.anthropic.com",
      "allowPaths": ["^/v1/messages$", "^/v1/messages/count_tokens$", "^/v1/models$"],
      "inject": { "header": "x-api-key", "valueFile": "/run/secrets/anthropic_api_key" }
    }
  ],
  "budget": { "maxRequestsPerHour": 600, "maxGlobalRequestsPerHour": 3000, "maxRequestBytes": 8388608 },
  "maxConcurrent": 8
}

Other SDKs work the same way: point their base URL at http://model-gateway:7434/<prefix> and use the gateway token.

Setup

umask 077; openssl rand -hex 32 > ~/.agentgate-model-gateway-token

Set ANTHROPIC_API_KEY_PATH and AGENTGATE_MODEL_GATEWAY_CLIENT_TOKEN_PATH in .env. The key file must be mode 600 and owned by the container's node user, or the gateway returns 503. It's re-read on every request, so rotation needs no restart.

Guarantees

  • Only GET and POST to paths matching an anchored allowPaths regex; everything else is 404. Dot segments, //, and encoded separators are refused.
  • Client auth headers, cookies, x-agentgate-*, and forwarding headers are stripped. Redirects are refused.
  • Responses stream back unchanged (SSE works), minus set-cookie and any header containing the injected key.
  • A separate token from agentd's, so a compromised gateway can't call the broker.

Budgets

maxRequestsPerHour is a token bucket per client IP; maxGlobalRequestsPerHour spans all clients. Both return 429 RATE_LIMITED with Retry-After. maxRequestBytes (default 8 MiB) caps bodies; maxConcurrent and maxBufferedBytes return 503 GATEWAY_BUSY.