Security
The chat protects three things: who a user is, where the chat may appear, and whose data a request may touch. This page says what holds each one and where your responsibility starts.
How it works
Section titled “How it works”Who holds what
Section titled “Who holds what”| Credential | Held by | Lifetime | Grants |
|---|---|---|---|
Server secret mcs_... |
your server only | until revoked; at most 2 active per app | the server API, and signing client and vouch tokens |
| Client token (HS256) | the browser, briefly | at most 600s; single use when it has jti |
one session exchange for that user |
| Session token (ES256, ours) | the chat frame’s memory | 15 minutes, renewed up to 12 hours after sign-in | the user API as that user, in that app |
| Guest token (ES256, ours) | your page’s localStorage |
400 days | coming back as the same guest, in that app only |
Webhook secret whsec_... |
your server only | until the endpoint is deleted | verifying our requests to you |
Dashboard cookie __Host-mc_dash |
the dashboard, HttpOnly | 7 days, extended on use | the dashboard, per tenant role (owner, admin, moderator) |
- A client token must have
issequal to the app id,alg: HS256, and atypofJWTor none. Any other algorithm is refused, and a vouch token (typ: vouch+jwt) cannot be used as a client token. It is checked against every active secret of the app. - A session refresh re-checks bans, whether the user still exists, and whether the tenant is suspended. A ban, a deletion or a guest merge also tells the open frame at once with
session.revoked, and it signs out. - Our public keys are at
/v1/.well-known/jwks.json. The session token’skidis the RFC 7638 thumbprint. - Server secrets and webhook secrets are encrypted at rest (AES-GCM, with a KMS key in production), bound to their row, and shown once when created.
Your token endpoint
Section titled “Your token endpoint”It is the front door: whoever gets a token from it is that user in the chat.
- Sign only for the user of the current session on your site. Never take the user id from the request.
- Keep
expshort (the quickstart uses 300s) and answer withCache-Control: no-store. - It is a same-origin
GETthat returns a token in the body. A page on another origin cannot read the answer, so do not add CORS headers to it. - Add
jtiif you want each token to work once. - Call
MondoChat('logout')when the user logs out of your site.
Where the chat may appear
Section titled “Where the chat may appear”The chat runs in an iframe on app.mondochat.io. Two independent checks keep it on your pages only:
- The frame’s
Content-Security-Policy: frame-ancestorslists the origins of that app (dashboard, Origins), so the browser refuses to render it anywhere else. - The frame reads the embedding page’s origin (
location.ancestorOrigins, else thedocument.referrerorigin, which embed.js guarantees withreferrerpolicy="origin") and refuses to start unless it is in the same list. It also refuses to run as a top-level page.
Page and frame talk only over a MessageChannel. embed.js hands over the port only after checking that the hello came from its own iframe and from https://app.mondochat.io, and the frame accepts only the first port from its parent. Every message is validated against the frame protocol. The token passes over that channel, and nothing secret is ever posted to *.
Guests
Section titled “Guests”A guest token lets anyone who holds it act as that guest. It lives in your page’s localStorage under mondochat:<appId>:guest, so a script injected into your page could copy it. It never gives access to a member account: members come only from your token endpoint. When a member signs in on a browser with a guest used in the last 30 days, the guest’s conversations move to the member (guests.merge_on_login).
Tenant isolation
Section titled “Tenant isolation”Every row belongs to a tenant. Postgres row-level security, forced even for the table owner, limits each transaction to one tenant, and the application role cannot bypass it. Anything from another tenant or app answers 404, never 403, so ids cannot be probed.
Calls from us to you
Section titled “Calls from us to you”- Webhooks and callouts are signed, and you should refuse unsigned or stale ones. See Verify signatures.
- They leave through an egress proxy that blocks private, loopback and cloud metadata addresses, so an endpoint URL cannot reach our internal network. Endpoint URLs must be https.
- Callout answers over 64 KB, or not matching the documented shape, are treated as failures.
Photos and videos are stored privately and served through signed URLs that expire after an hour. Moderators see evidence through URLs that expire after 120 seconds, and a flagged video’s after an hour so it plays to the end. Evidence is deleted within 24 hours of an allow, and 30 days after a case is dismissed. The retention settings delete messages, photos and IP logs after the periods you set.
What we host
Section titled “What we host”We host adult content. Sex, nudity and erotic talk between adults are normal here and are never moderated. Moderation looks for what is illegal or harmful whatever the setting: minors, trafficking, sexual coercion, violence, hate, fraud and the rest listed under moderation in the settings reference. It runs after delivery and, by default, only opens cases for your moderators. You choose per category whether findings are also hidden or removed. See Moderation.
Limits
Section titled “Limits”- Per-user limits (
limits,perms) and per-app request rates (rate_limits) come from your settings. - Bans work by user or by IP range. The client IP is the one CloudFront saw, not a header anyone can send.
- A tenant in
teststatus accepts at most 50 users per app. - Alert rule tests: 5 per rule per hour.
- A client token lives at most 600 seconds and an app has at most 2 active server secrets.
Where it ends
Section titled “Where it ends”- The chat does not verify ages. Checking that your users are adults is your site’s job, before your server signs a token for them.
- The chat trusts your token endpoint. A token your server signs for the wrong user puts the wrong user in the chat.
- A guest token in
localStorageis only as safe as your page’s scripts. - mondochat stores no end-user email.