Appearance
Security
Handoff checklist for platform and security reviewers. Implementation lives under apps/server/src/ (middleware, routes, env.ts).
Production boot guards
When NODE_ENV=production, the server refuses to start unless:
| Requirement | Why |
|---|---|
REQUIRE_JWT_EXP=1 | Reject session JWTs without exp |
REQUIRE_S3=1 | Durable uploads (no ephemeral container disk) |
Non-empty CORS_ORIGINS | Explicit browser allowlist |
JWT_SECRET ≥ 32 chars | Client session signing |
ADMIN_SESSION_SECRET ≥ 32, ≠ JWT | Admin session signing |
FILE_URL_SECRET ≥ 32, distinct | Public file URL HMAC |
No custom INTERCOM_BASE_URL | Prevents token exfiltration via API override |
See assertProductionSecurity() in apps/server/src/env.ts.
Client auth (SDK)
- HS256 JWT in
Authorization: Bearer. - Claim:
userIdorsub. - Mint only on the host backend with the shared
JWT_SECRET. - Resource routes check the contact owns the conversation / ticket / file (IDOR → 403). Covered by server tests.
Admin auth
- Password verified against
admin_users(scrypt). - Session = HMAC token (
ADMIN_SESSION_SECRET); deactivated users fail verification. - Login lockout by IP (in-memory) — configure
TRUST_PROXY_HOPSbehind proxies. - Permission gates on mutating admin routes.
Files
- Upload allow-list by magic bytes (JPEG/PNG/GIF/WEBP/BMP/PDF/MP4/TXT), size caps (
MAX_FILE_MB/ image / video). - Authenticated
GET /api/files/:idenforces ownership. - Public
GET /files/:id?t=requires a key-bound HMAC (FILE_URL_SECRET). Treat leaked URLs as temporary bearers.
Webhooks
POST /webhooks/intercomverifiesX-Hub-Signature(SHA1) withINTERCOM_CLIENT_SECRET.- Events deduped in
processed_events.
Content safety
- Operator HTML → typed rich-text tree in the SDK (no
dangerouslySetInnerHTMLin chat UI); URL schemes allow-listed. - Channel comments: no links, DLP (seed / key / card / password) with redacted moderation events.
Devtools
/api/devtools (contact wipe) mounts only when ENABLE_DEVTOOLS=1 andNODE_ENV !== "production". Bulk wipe also needs ENABLE_DEVTOOLS_RESET_ALL=1. Leave both empty on shared stands and production.
Secrets rotation
| Secret | Effect of rotate |
|---|---|
JWT_SECRET | All client session tokens invalid |
ADMIN_SESSION_SECRET | All admin sessions invalid |
FILE_URL_SECRET | Existing public file links fail verify |
| Intercom / S3 / FCM / Telegram | Update env + redeploy; rotate at provider |
Logging
Message bodies, raw files, and seed-like secrets must not appear in server logs. Diagnostics accept device metadata only.
Related docs
- Configuration — env reference
- Data model — where PII lands
- Operations — backup / incident notes