Security
Passwords
- bcrypt, cost 12 — the only accepted hashing for customer passwords.
- Timing-safe login — a dummy bcrypt compare is performed even when the email does not exist, so the login path takes the same time whether or not the user is real. This prevents user enumeration and timing attacks.
API keys
- HMAC-SHA256 — the server stores
HMAC-SHA256(secret), never the raw secret. - Dual-secret rotation —
API_KEY_SERVER_SECRETandAPI_KEY_SERVER_SECRET_PREVallow graceful server-side secret rotation without invalidating live keys. - Constant-time comparison — key and webhook signatures are compared with
crypto.timingSafeEqual. - Raw key shown once — the raw value is returned only at creation and cannot be recovered.
Sessions
- Cookie
runne_session:HttpOnly,SameSite=Lax,Securein production,Max-Age24h. - jti revocation — logout writes the session
jtito a Redis denylist with the token's remaining lifetime; every request re-checks it. - JWT claims are validated, and the
isAdminclaim is not trusted for authorization — admin checks read the customer row from the database.
CSRF (fail-closed)
Unsafe methods (POST, PUT, PATCH, DELETE) are protected by an Origin allowlist:
Originpresent but not allowed →403.Originabsent but a session cookie is present →403(fail-closed).Originabsent with no cookie → allowed (webhooks,x-api-key, server-to-server).
The allowlist is derived from FRONTEND_URL (comma-separated).
Rate limiting
- Per-API-key sliding window (Redis Lua; in-memory fallback) on chat completions.
- Per-IP auth rate limiting on login (10/min) and register (5/min) against credential stuffing.
Workspace isolation
- Every workspace-scoped query checks ownership (
workspace.customer_id == session.customer_id); mismatches return403/404. - Suspended customers and workspaces are rejected by the gateway before processing.
Financial safety
- Atomic operations: balance update + ledger insert in one transaction.
- Conditional
UPDATE … WHEREprevents over-spend and double-credit. - Webhook idempotency (status check + conditional update + unique index) prevents double-crediting.
- Receipt sending is idempotent and best-effort (retried by cron, never fails the webhook).
Known limitations (beta)
models:readscope is not yet enforced (model catalog is public).- Quota fields other than
tokens_per_monthare declared but not enforced. - Webhook signature relies on
YOOKASSA_WEBHOOK_SECRET; rotate it alongside the server secrets.
Related
- Authentication — key and session details.
- Principles — capability grants.
- Rate Limits & Quotas — enforcement.