Skip to content

Auth

Inbound authentication, tenant derivation, and isolation defenses. Source: felix.auth in felix-run/felix.

Modes

FELIX_AUTH_MODE:

Mode Behavior
none Anonymous principal (tenant_id=default). Requires FELIX_ALLOW_INSECURE=true outside development when binding a non-loopback host
api_key `Authorization: Bearer
jwt Bearer JWT verified via FELIX_JWT_VERIFIERS + JWKS (FELIX_JWKS_PUBLIC for self-issued)

Public paths stay open in all modes: /health, /metrics, /docs, /openapi.json, /redoc, /.well-known/*.

Missing credentials in api_key / jwt401 (fail closed). Mint self-issued tokens with felix mint-jwt.

Manifest gates

Routes call enforce_inbound_auth (via prepare_tenant_invoke):

  • inbound.allow_anonymous: false → reject anonymous
  • inbound.required_scopes → all must be present on the principal → else 403

Management routes use require_scope (audit:read, manifests:write, approvals:decide, …).

RequestContext

Auth middleware installs a contextvars-backed RequestContext: settings, principal (tenant / scopes / subject), and a fresh LimitState. Tool wrappers and patterns read it via get_context() / try_get_context() — no parameter threading, harder to tamper.

Tenant derivation (JWT)

  1. custom:tenant_id claim
  2. tenant_id claim
  3. First label of issuer host
  4. Else default (also used for anonymous / api_key default meta)

Internal consumers

POST /internal/* is self-authenticating: shared secret header (FELIX_CONSUMER_SHARED_SECRET / X-Felix-Consumer-Secret) with constant-time compare. Required when auth_mode ≠ none (and in production).

Thread-id smuggling

Caller-supplied thread suffixes (body.thread_id, x-thread-id) reject : and #. The server always prefixes {tenant_id}:.

Outbound

Manifest MCP / peer / container auth refs resolve outbound headers (bearer literals or OAuth client-credentials). Tokens may be cached encrypted at rest when FELIX_OAUTH_CACHE_KEY is set. Outbound hosts pass SSRF allow-list checks before a bearer is attached.

Local Compose defaults to FELIX_AUTH_MODE=none + FELIX_ALLOW_INSECURE=true. Production: prefer jwt or api_key and keep ALLOW_INSECURE false — see deploy.