Appearance
Security
Allowed origins
A bot's public id is in your page's source, so anyone can copy it. Its allowed origins (security.allowedOrigins, in the bot's Security tab) decide which sites may use it:
| Pattern | Matches |
|---|---|
https://www.example.com | Exactly that origin |
https://*.example.com | Any subdomain over https (not example.com itself) |
example.com | https://example.com and http://example.com |
http://localhost:* | localhost on any port |
* | Any site |
An empty list allows any site; the admin panel warns about it. The list is enforced three times:
- the bot's settings are only given to pages on those origins (others get
origin_not_allowed); - the chat window is sent with a
frame-ancestorspolicy listing them, so the browser refuses to show it framed by any other site; - the WebSocket checks the page the chat is on when it connects.
Origins are scheme, host and port: no paths. Publish after changing them.
Keys and secrets
- Provider keys, webhook secrets, identity secrets, tool secrets and MCP headers and environment are encrypted at rest (AES-256-GCM) with the master key, each bound to the row it belongs to. They are never sent to a browser. The master key can be changed: see Backups.
- API tokens, sessions and visitor tokens are stored as hashes.
- Passwords are hashed with scrypt; sign-in is limited to 8 attempts per IP and email address every 5 minutes.
- The admin panel's session cookie is
httpOnlyandSameSite=Lax, and changes need a CSRF token. - Server logs leave out authorization headers, cookies, keys, passwords, tokens and secrets.
- The audit log (admins:
GET /api/v1/audit, newest first,{ entries, nextBefore }for paging) records who changed what: bots (created, copied, published, paused or set live, restored, deleted, identity secret replaced), custom faces, provider connections, HTTP tools, MCP servers, secrets, knowledge sources, webhooks, API tokens, team members and invitations, workspace settings, password changes, conversation take-overs, releases and deletions, deleted leads and erased visitors. It records the target, never the request body (which can hold keys).
Visitors and the agent
- Identity: with
security.identity.mode: required, only visitors your server vouches for can chat. See Identity verification. - Untrusted input: what visitors write, page context, knowledge passages and tool results reach the agent marked as information, never instructions, and the agent's fixed rules tell it to ignore attempts to change its role or reveal its instructions.
- Actions: tools that change things can ask the visitor first.
- Images are decoded and re-encoded before storage (no EXIF, nothing hidden in the file).
- The agent says it's an AI when asked, and won't identify people from their face or guess sensitive traits.
Rate limits and caps
| Limit | Default | Where |
|---|---|---|
| Chat connections per IP | 30 a minute | Server (fixed) |
| New conversations per IP, per bot | 30 an hour | security.rateLimits.conversationsPerIpPerHour |
| Messages per visitor | 12 a minute | security.rateLimits.messagesPerMinute |
| Image uploads per visitor | 30 an hour | security.rateLimits.uploadsPerHour |
| Message length | 4,000 characters (the message box stops there too) | security.maxMessageChars |
| Conversation length | about 600 messages | security.maxTurnsPerConversation (200) |
| Daily spend, messages, voice minutes | none | security.caps |
| Voice conversations at once | 5 per bot | security.caps.concurrentVoiceSessions |
| Camera frames | about 2 a second per conversation (bursts of 4), 512 KB each | Server (fixed) |
Behind a proxy, set TRUST_PROXY=true so per-IP limits see visitors' own addresses, not the proxy's. Spending caps are the main protection against a bot being used heavily on your bill: set them.
Requests to your network (SSRF)
HTTP tools, MCP servers (over HTTP), webhooks and the knowledge crawler make requests to addresses admins enter. To keep a bot from being turned against your own network, the server refuses localhost and private, loopback, link-local (including cloud metadata at 169.254.169.254), carrier-NAT, multicast and reserved addresses. It checks the address a host name actually resolves to when it connects, so DNS tricks don't get around it. Only http and https are allowed, and at most 3 redirects are followed (each checked the same way).
- Allow private network on one HTTP tool lets that tool reach your internal API.
ALLOW_PRIVATE_NETWORK=truelifts the rule for everything (tools, MCP servers, webhooks, the crawler).
stdio MCP servers
With ALLOW_STDIO_MCP=true, admins can add MCP servers that run as commands on the server. Each gets a minimal environment (PATH, HOME and the like) plus the variables the admin enters, never the server's own settings and keys. But a command can do anything the server's user can, so this is shell access for anyone with the admin role. Leave it off unless every admin is trusted with that.
Production mode
Run with NODE_ENV=production (the Docker image does). In development mode the server also accepts the chat's WebSocket from localhost pages, allows http:// webhook URLs and serves source maps.