Persona

Security model

Read this before you go live. It explains what each credential can do, how we protect it, and what you must do on your side.

Tenancy

Every record (persona, knowledge, conversations, keys, settings, subclients) belongs to exactly one client and every query is scoped to it. A credential for one client can never read or write another's data:

  • Whop: the member's Whop token is verified against Whop's keys on every request, and we ask Whop whether that user can access the exact experience. The business is read from Whop, never from the request.
  • Dashboard: your session must belong to an email on that business's team, checked on every request.
  • API keys and sessions: the client is derived from the key or token, never from the request body.

Credentials and how they're stored

CredentialFormatLifetimeStorage
Secret keysk_live_ + 32 random characters (~190 bits)Until revokedSHA-256 hash only
Publishable keypk_live_ + 32Until revokedSHA-256 hash only
Chat sessioncs_ + 32≤ 24 h (anonymous 2 h)SHA-256 hash only
Dashboard sessionses_ + 32, HttpOnly cookie30 days, revoked on sign-outSHA-256 hash only
Sign-in linkml_ + 3215 min, single useSHA-256 hash only

Full keys are shown once, at creation. A copy of our database alone gives an attacker no usable credential.

What each key can do

  • Secret key: create and end chat sessions for any user id you send. It's equivalent to "log in as any of my users" for the chat, so treat it like a database password: server-side only, in a secret store, never in front-end code, mobile apps or git. Requests carrying a browser Origin header are refused.
  • Publishable key: start anonymous sessions only, only while anonymous chat is on, only from your allowed sites, rate limited. It can't impersonate a signed-in user or read anything.
  • Chat session: chat as one subclient of one client until it expires. It can't list or read other conversations, knowledge or settings.

Getting a token to the browser safely

  • Your server calls POST /api/v1/sessions after your own login check, and returns only the cs_ token to that user.
  • The widget passes the token to the chat frame with postMessage, addressed to our exact origin. The frame accepts tokens only from your allowed sites.
  • Hosted links carry the token in the URL fragment, which is never sent to servers or included in Referer, and the page removes it from the address bar.
  • Tokens are never placed in URL query strings, cookies on your domain, or logs.

Framing and origins

  • /chat/<client> sends Content-Security-Policy: frame-ancestors 'self' <your allowed sites>, so no other website can frame your chat (clickjacking, quota theft).
  • The dashboard can only be framed by Whop. Other pages can't be framed at all.
  • Cross-origin API calls are only answered with CORS headers for your allowed sites.

Dashboard protections

  • Passwordless sign-in, so there are no passwords to leak or reuse.
  • The session cookie is HttpOnly, Secure, SameSite=Lax; scripts can't read it.
  • Every state-changing dashboard request must come from the same origin (CSRF protection), on top of SameSite.
  • Sign-in responses are identical whether or not an email has an account, and are rate limited per IP and per email.

Rate limits and abuse

  • Anonymous sessions: 20 per IP per hour, 2,000 per client per hour.
  • Sign-in emails: 10 per IP and 5 per email per hour.
  • Replies: your plan's monthly allowance, enforced atomically, so bursts can't exceed it.

Integrations

Connected apps (Fireflies, Drive, Notion…) are reached through Composio with read-only tools; we never store their tokens. Their content reaches subclients only when you import it into knowledge, or for the few apps you explicitly switch on for live use.

Your checklist

  • Secret keys only in server environment variables or a secret manager.
  • Your session endpoint requires your own login and uses your user id for subclient.id, never a value from the request.
  • Allowed sites list only origins you control, all https://.
  • Anonymous chat on only if you want public visitors.
  • On sign-out, Persona.destroy() in the page and DELETE /api/v1/sessions on the server.
  • Revoke and replace a key immediately if it may have leaked (Embed and API, API keys). Revocation is instant.
  • Short expires_in for sensitive apps; the widget renews automatically.

Reporting a vulnerability

Email security issues to the team that runs your Persona account. Please don't test against other clients' data.