Skip to content

Security

Lef edited this page Oct 4, 2026 · 3 revisions

Security

How logins, sessions and the public site are protected. The full technical description is docs/SECURITY_MODEL.md. To report a vulnerability, see SECURITY.md.

Accounts

  • Registration is invite only (backend/invites.json, see Configuration). Invite codes are claimed atomically, and guessing them is rate limited.
  • Passwords: at least 12 characters, at most 72 bytes, not on a list of ~2,300 common passwords, and not your username or email. Stored with bcrypt (cost 12).
  • Wrong username and wrong password get the same answer in the same time, so nobody can find out which accounts exist by logging in.

Brute force

  • Lockouts that back off but always expire (at most 15–60 minutes): per account + address, per account, per address, and per address for wrong invite codes.
  • Addresses an account has signed in from in the last 90 days skip the account-wide lock, so someone who knows your username can't keep you locked out.
  • Rate limits on login, registration, password changes, AI requests, messages and dice. You get a clear "try again in N seconds" message, not a logout.
  • Admins can lift a lock in Admin → Logins & lockouts (by username or one exact IP address), which also has a paged, filterable login audit. The API is POST /api/admin/auth/unlock.

Sessions

  • Short access token in the browser plus a refresh token in an HttpOnly cookie that only the auth endpoints receive. Each refresh token works once; reusing an old one ends that whole login.
  • Log out really logs out (the token is revoked on the server). Sign out everywhere revokes every session.
  • Changing your password, being banned or deactivated, or a role change ends all your sessions immediately (a database trigger bumps your token version).
  • Every login, failed login and lockout is recorded (auth_events, readable by admins).
  • Access tokens last 30 minutes; the browser refreshes them on its own.
  • Log lines can't be forged: control characters in anything a client sends (usernames, paths, error messages) are escaped before they reach the logs.

The public site

The maintainer's instance at https://srai.srv-box.com goes through a reverse proxy (TLS, HSTS) to the machine running the stack. There's no extra gate in front of it anymore (removed on 2026-10-04): everything is behind the app's own login, invite-only registration, rate limits and lockouts described above. If you run your own public instance, put HTTPS in front of it the same way.

The machine

  • Only nginx (:80) is reachable from the network; the database, Redis, ChromaDB and the backend listen on localhost.
  • The backend runs on gunicorn with debug off and refuses to start in production with weak secrets.
  • Errors never include internal details; API responses aren't cached.

Game integrity

  • Dice are rolled and posted by the server, so a dice card in the chat is always a real roll.
  • Only the AI (with a one-time grant issued alongside a real reply) or an admin can post as the Storyteller.
  • OOC-room moderation can't be talked out of a verdict by text inside the message.

Clone this wiki locally