Authentication & Security

Arona authenticates callers on two tracks: JWT session tokens for interactive clients (the chat + admin UI, RPC calls) and API keys (arona-…) for programmatic OpenAI-compatible traffic. A separate admin token guards the administrative surfaces. This page documents the mechanics, the security model, and the known low-risk leftovers from a security audit.

JWT sessions

Sessions use JWT access/refresh token pairs issued by the kirino_session token manager:

Access tokens authenticate the JSON-RPC plane (/api/rpc) and GET /v1/models; the SSE sidecar (/api/rpc/events) is keyed by its session id, a capability minted during authenticated RPC calls rather than a bearer credential. The /v1/chat/completions, /v1/embeddings and /v1/video/* endpoints require an API key (a JWT is not accepted there). Access tokens are short-lived so a stolen token is usable only briefly. Refresh tokens are exchanged for fresh pairs via auth.refresh.

Refresh uses token-family rotation: consuming a refresh token invalidates it and issues a new one, and reusing a consumed refresh token revokes the whole family — auth.refresh answers with AUTH_ERROR and the message Refresh token reused (the underlying error is TokenReused, "refresh token has been reused — token family revoked"), and the account must log in again. Family revocation is in-memory (a revoked_families set): a server restart clears it, so the protection is best-effort across restarts (per-user session state does not survive a restart).

The signing secret comes from the JWT_SECRET environment variable. Outside MOCK_MODE=1 the server refuses to start if JWT_SECRET is unset or still equals the built-in development secret, so a production instance can never accidentally serve tokens signed with a public constant. Use a strong, random secret and never commit it.

API keys

API keys are the machine credential for the OpenAI-compatible surface:

Admin tiers

There are three distinct admin gates, each with its own credential:

  1. 1
    /api/admin/* routes — backend and alias management (`POST/GET/DELETE /api/admin/backends`, `POST/GET/DELETE /api/admin/aliases`) require the `Authorization: Bearer ARONA_ADMIN_TOKEN` header. When `ARONA_ADMIN_TOKEN` is unset, `check_admin` always fails and every admin route returns **401 "Admin access required"** — the whole management surface is disabled rather than opened up.
  1. 1
    agents.* and engine.invoke RPC methods — the agent cluster and engine control plane require a JWT whose account has `users.is_admin = true`. An authenticated non-admin is rejected with the implementation- defined code **-32007 (`ADMIN_REQUIRED`)** plus a method-specific hint (e.g. `agents.deploy starts model deployments on GPU nodes`); an **unauthenticated** caller gets the standard **-32005 (`AUTH_ERROR`)** so the server does not reveal that the method is privileged at all.
  1. 1
    billing.plan.set and billing.video.pricing.set RPC methods — billing mutations require the same Bearer `ARONA_ADMIN_TOKEN` as the admin HTTP routes; without it they return `AUTH_ERROR` "Admin access required".

The first registered user becomes the admin (users.is_admin = true). Every later registration is a regular user, and registration is only open while ARONA_REGISTRATION_OPEN is set to a truthy value.

Password policy

Passwords must satisfy both rules (enforced at registration and on any password-change path):

Rate limiting

Rate limiting runs on two independent tracks; either one can reject a request with 429:

1. In-memory sliding window (per identity)

Every authenticated /v1 request passes an in-memory sliding-window limiter keyed by the caller's identity:

The default budget is 60 requests per minute, overridable with ARONA_API_RATE_LIMIT_RPM (set higher for agent pipelines that fan out many parallel LLM calls). Setting it to 0 blocks every request.

2. Tier rate limit (per key, from the database)

Billing tiers carry a per-key rate_limit_rpm. The check counts usage_records rows for the key's prefix in the trailing 60 seconds (usage is persisted after each response, so the window lags by at most one in-flight request; DB failures fail open). The seeded free tier is 10 RPM; pro/enterprise tiers raise the ceiling. Monthly quota enforcement shares the same rejection path.

Login rate limiting

Credential-guessing is throttled at the login endpoint: 5 failed attempts per 5-minute window per email and 20 per 5-minute window per IP, each followed by a 15-minute lockout.

Retry-After

Every 429 response carries a Retry-After header so OpenAI-compatible clients back off instead of hammering the endpoint: quota rejections set it to seconds until the end of the month; rate-limit rejections set it to 60. See Billing & Usage for the quota model.

Security model notes

Known low-risk leftovers (from the audit)

The following are documented as-is; they are intentional or accepted for now, but worth knowing when you expose an instance beyond a trusted network: