Skip to main content

Responses Proxy security

Treat Responses Proxy as an infrastructure service. It handles provider credentials, client prompts, model outputs, request metadata, response state, and telemetry history.

Do not commit

  • .env
  • .env.service.local
  • secrets.md
  • database files under data/
  • real provider credentials
  • real Morph org tokens, service delegation values, or proxy keys

Use placeholders in examples:

replace-with-provider-key
postgresql://user:password@host/database?sslmode=require

Admin API

All /admin/* routes require Morph bearer auth except Claude Code OAuth setup completion. Direct callers use a Morph org token; the Morph Cloud frontend BFF and deploy automation use a service delegation token.

Authorization: Bearer <SEELE_ORG_TOKEN_OR_DELEGATION_TOKEN>

Missing or invalid credentials return 401, valid credentials without allowed org membership return 403, and admin auth misconfiguration or dependency failures return 503. There is no static admin token mode.

Providers, model aliases, and proxy keys are scoped to the caller's Morph organization.

Routes outside /admin/* are:

  • /healthz
  • /readyz
  • /openapi.json
  • /docs
  • /v1/responses

/v1/responses and Claude Code gateway routes under /v1/messages and /v1/models are not admin routes, but they still require a valid org-scoped proxy key.

Frontend BFF

The Morph Cloud dashboard calls the proxy through a same-origin BFF. The browser never receives backend admin credentials.

The BFF:

  • obtains Morph service delegation auth for admin and registry calls
  • validates the active organization before proxying
  • sanitizes forwarded request, trace, and org headers
  • validates $SECRET provider credential references against the organization's secrets
  • strips secret-shaped fields from admin responses before they reach the browser
  • rejects cross-origin mutations before forwarding

Backend admin routes should still enforce bearer auth because the BFF is not the only possible caller.

Data-plane proxy keys

POST /v1/responses requires an org-scoped proxy key:

Authorization: Bearer <rprx-...>

Requests without a valid key return 401. The key binds routing to its owning org's providers and aliases. Key secrets are returned once at creation, stored hashed, and never echoed by list APIs.

Positive proxy-key org resolution is cached in each API process for PROXY_PROXY_KEY_CACHE_TTL_SECONDS, which defaults to 30 seconds. Revoked keys may continue to authenticate until that cache entry expires. Set the TTL to 0 only when immediate revocation matters more than hot-path efficiency. Invalid keys are cached for at most 5 seconds.

Claude Code gateway routes use the same proxy-key records but read the key from X-Responses-Proxy-Key by default. This prevents a collision with Anthropic Authorization or x-api-key credentials that Claude Code may already send.

Provider credentials

Provider registration supports:

  • provider credential rows under /admin/providers/{provider_id}/credentials
  • api_key: write-only inline credential encrypted before SQL storage
  • credential_ref: external reference such as env:PROVIDER_API_KEY
  • $PROVIDER_API_KEY in the console API-key field, which the BFF converts to credential_ref=env:PROVIDER_API_KEY after validating the org secret exists
  • Env-sourced provider pools such as OPENAI_API_KEY, OPENAI_API_KEY_1, and OPENAI_API_KEY_3

Inline credentials require:

PROXY_SECRET_ENCRYPTION_KEY=...

Keep this value stable. If it changes, existing encrypted credentials cannot be decrypted. Admin responses should expose configured/resolved booleans such as api_key_configured, credential_configured, credential_resolved, resolved_key_count, and pool_size, never raw credential values.

Env-sourced provider keys are deployment-global and resolve only for PROXY_BOOTSTRAP_ORG_ID by default, or for org ids listed in PROXY_ENV_CREDENTIAL_OWNER_ORG_IDS. Use org-distinct provider ids when different organizations need separate physical provider credentials.

Credential rows are the preferred BYOS storage path. Rows are org-scoped, provider-owned, and can be rotated or deleted without changing model aliases. If an enabled credential row resolves, routing uses credential rows only. If no enabled credential rows exist, the provider falls back to env slots, provider-level encrypted api_key, then provider-level credential_ref.

credential_ref accepts only env:NAME references where NAME matches *_API_KEY or *_API_KEY_<number>, unless the name is explicitly listed in PROXY_CREDENTIAL_REF_ENV_ALLOWLIST. Sensitive env names such as MORPH_API_KEY, PROXY_*, DATABASE_*, and SERVICE_* are blocked.

Request forwarding

Incoming client Authorization is not forwarded upstream. The proxy injects the selected provider credential into the provider's configured auth header after resolving the registered model alias.

Data-plane clients send proxy keys, not provider keys.

For Claude Code registered-alias mode, inbound Anthropic auth headers are stripped and replaced with the selected registered provider credential. Direct passthrough mode forwards caller Anthropic auth only for model ids explicitly allowlisted by PROXY_CLAUDE_GATEWAY_DIRECT_MODELS.

Claude Code OAuth escrow

Claude Code stored-subscription setup is gated by PROXY_ENABLE_CLAUDE_CODE_TOKEN_ESCROW=true and requires PROXY_SECRET_ENCRYPTION_KEY.

Authenticated org members create setup sessions through /admin/claude-code/oauth/sessions. The returned setup token is shown once, stored only as a hash, bound to the session/provider/org, expires quickly, and cannot be replayed after completion or cancellation. The completion route accepts that setup token instead of Morph admin auth so the local teleport command can run outside the Morph dashboard.

Stored OAuth credential bundles are encrypted in SQL as kind="claude_code_oauth" provider credential rows. Admin APIs expose metadata, configured booleans, validation status, expiry, and needs_reauth, never bearer or refresh tokens. Server-side refresh uses deployment-owned PROXY_CLAUDE_CODE_OAUTH_REFRESH_* settings only; the frontend and CLI cannot supply refresh endpoints.

Claude Code OAuth credentials are pinned to provider_type="anthropic" and https://api.anthropic.com/v1. Use normal provider credentials for third-party Anthropic-compatible gateways.

Telemetry payload capture

Request and response body persistence is disabled by default:

PROXY_CAPTURE_REQUEST_BODY=false
PROXY_CAPTURE_RESPONSE_BODY=false
PROXY_MAX_CAPTURE_BYTES=2000000

For Chat Completions and Anthropic providers, reconstructed response state is also stored in SQL so previous_response_id continuations work.

Request and decision rows are persisted inline for dashboard freshness; raw telemetry events are also written through the Redis stream and worker. Token usage is persisted separately when providers return usage metadata. Operational guidance:

  • Treat SQL as sensitive application data.
  • Reduce PROXY_MAX_CAPTURE_BYTES in sensitive environments.
  • Add redaction before sending customer secrets through production environments.

Deployment secrets

Required deploy secrets include Morph credentials plus:

  • PROXY_DATABASE_URL
  • PROXY_BOOTSTRAP_ORG_ID
  • PROXY_ADMIN_DELEGATION_SERVICE_API_KEY
  • PROXY_ADMIN_SMOKE_MORPH_API_KEY
  • PROXY_SECRET_ENCRYPTION_KEY
  • Optional PROXY_ENABLE_CLAUDE_CODE_TOKEN_ESCROW
  • Optional PROXY_CLAUDE_CODE_OAUTH_REFRESH_*
  • Optional PROVIDER_API_KEYS_ENV for forwarded provider key slots

Frontend server deployments also need:

  • service-delegation access for agent-responses-proxy with responses:admin
  • MORPH_RESPONSES_PROXY_BASE_URL or VITE_RESPONSES_PROXY_BASE_URL when overriding the default backend URL
  • Optional MORPH_RESPONSES_PROXY_DELEGATION_AUDIENCE

Scripts should confirm secret presence without printing raw values.