Security
How this platform protects your server, your data, and itself.
Reporting a vulnerability
Email [email protected] with enough detail to reproduce the issue. Please do not open a public issue, and please do not test against a server you do not own. We will confirm receipt, keep you updated, and credit you if you would like that.
What we hold, and how
- Discord OAuth tokens — encrypted with AES-256-GCM at rest, never sent to the browser, and only ever used to read your identity and the servers you administer.
- Sessions — a signed JWT in an
HttpOnly,Secure,SameSite=Laxcookie that points at a server-side record. Revocation is immediate; signing out everywhere invalidates every device on the next request. - API keys — only a scrypt hash is stored. The key is displayed once at creation and cannot be recovered, only rotated.
- Sensitive identifiers (FiveM licences, Steam IDs, IPs) — stored as a keyed hash for matching plus an encrypted copy for authorised disclosure during an appeal. The plaintext is never queryable and never rendered.
- Client IPs — pseudonymised with a keyed hash before they reach the audit log. The raw address is not stored.
Controls in the application
- Every API route authorises server-side against the database — never against a claim in a token or a value from the client.
- Changing a server ID in a URL cannot return another server’s data; “not found” and “no access” are deliberately indistinguishable.
- CSRF protection is an origin check plus a double-submit token, on top of
SameSite. - Every input is validated with Zod; every database query is parameterised through Prisma.
- Outgoing requests (webhooks, FiveM checks) are validated against an SSRF policy that resolves DNS and rejects private ranges, re-validating on every redirect.
- Rate limits apply per user, per server, per API key and globally, backed by Redis so they hold across processes.
- Secrets are redacted from structured logs and from audit-log diffs by a shared sanitiser.
- Error responses carry a request ID, never a stack trace, in production.
Deliberate design decisions
- Discord Administrator does not bypass protection. Only an explicit whitelist entry does. Granting yourself Administrator is the first move after compromising a staff account, so treating it as trust would defeat the product.
- The bot does not request the Message Content intent. No protection reads message bodies, so asking for them would only enlarge what a compromise of this bot could expose.
- The invite does not request Administrator. It asks for the specific permissions each protection needs, and tells you what stops working without each one.
- There is no in-memory fallback for anti-raid counters. A per-process fallback would give each process its own view of a raid. If Redis is down, the service reports itself not-ready instead of protecting inconsistently.
Self-hosting
This platform can be self-hosted. In that case your PostgreSQL and Redis hold all of the above, and the security of the deployment is yours — see the deployment documentation for the production checklist.