Security Model
How we protect login, sessions, and writes against the usual web hazards.
Discord OAuth2 login
- Scopes requested:
identify+guilds. We never ask foremail,connections, or anything that lets us act as the user. - The OAuth
stateis a 256-bit URL-safe random token, single-use, expiring after 10 minutes. - State is stored server-side in the signed session cookie, not the URL.
- The callback drops the
codefrom the URL via a redirect to/serversimmediately after exchange. - Tokens are never written to disk; we only hold them on the server-side session.
Session cookies
HttpOnly: JavaScript can't read it.SameSite=Lax: sites can't auto-submit cross-origin POSTs.SecurewhenDASHBOARD_BASE_URLis HTTPS, so the cookie never travels over plaintext.- The signing key comes from
DASHBOARD_SECRET_KEY; if unset we boot with a fresh random one and warn loudly.
CSRF
Every non-safe HTTP method must carry a matching
X-CSRF-Token header (or csrf_token form
field). Tokens are 256-bit URL-safe randoms, per-session, and
compared with hmac.compare_digest. The dashboard's
JavaScript wrapper attaches the token automatically.
Authorization layers
- Logged in? If not, we redirect to
/auth/login. - Token expired? We refresh transparently with the stored refresh token. If refresh fails, the session is wiped.
- Bot in guild? The middleware aborts with 404 if the bot isn't actually present.
- User has admin? Discord Manage Server or Administrator, intersected with the bot-admin role list.
- Write rate limit? Embed sends and ticket-panel posts are rate-limited per actor / guild.
Server-side validation
- Every role / channel / emoji submitted via the API is verified against the live guild before any write.
- Embed image URLs must be HTTPS and not point at private IP space (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8, link-local). - Every JSONB write goes through a per-feature whitelist when one exists.
- Length caps mirror Discord's: title ≤256, description ≤4096, fields ≤25, etc.
What we don't do
- We never log access tokens, refresh tokens, or the OAuth state.
- We never expose another guild's data. Every endpoint scopes by the URL's
guild_idand re-checks membership. - The dashboard does not accept arbitrary HTML from users. Embed text is rendered as text-with-a-tiny-markdown-subset, never as raw HTML.