What the platform does by default, and what you still have to do on your own server.
Built in
| Area | Behaviour |
|---|---|
| Stored XSS | Rich text — tickets, notes, notification templates — is purified against an allow-list before storage, not just when rendered. |
| CSRF | Stateful sessions require the XSRF token header on every write. |
| Rate limiting | Auth and licensing endpoints are throttled separately from the rest of the API. |
| Webhooks | Only configured gateways are routable, and payloads are signature- and timestamp-verified. |
| Tenant isolation | A global scope constrains tenant data, and a session whose tenant no longer matches the user is invalidated. |
| Passwords | Hashed by cast, so a plaintext value cannot be written to the column by accident. |
| Secrets at rest | Gateway and Turnstile credentials are stored encrypted in settings, not in plain columns. |
Your responsibility
- APP_DEBUG=false in production. A debug page discloses environment variables.
- HTTPS everywhere, with TRUSTED_PROXIES set so the app sees real client IPs.
- Restrict database and Redis to the local network. Neither should be reachable publicly.
- Require MFA for staff — the workspace policy can enforce it per role.
- Enable Turnstile on login, registration and password reset if your portal is publicly reachable.
- Keep backups off the server, and rehearse a restore.
NoteSelf-hosted licensing is deterrence, not DRM. Whoever runs the server controls the environment and the source, so treat licence checks as a commercial boundary rather than a security control.