Skip to content

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 rotationAPI_KEY_SERVER_SECRET and API_KEY_SERVER_SECRET_PREV allow 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, Secure in production, Max-Age 24h.
  • jti revocation — logout writes the session jti to a Redis denylist with the token's remaining lifetime; every request re-checks it.
  • JWT claims are validated, and the isAdmin claim 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:

  • Origin present but not allowed → 403.
  • Origin absent but a session cookie is present → 403 (fail-closed).
  • Origin absent 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 return 403/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 … WHERE prevents 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:read scope is not yet enforced (model catalog is public).
  • Quota fields other than tokens_per_month are declared but not enforced.
  • Webhook signature relies on YOOKASSA_WEBHOOK_SECRET; rotate it alongside the server secrets.