Critical warning for users and app builders
Product and docs copy you should show
If your app requests DM-related OAuth scopes (dm.read, dm.write, and related scopes), pair the OAuth consent screen with clear product language:
- Encryption keys stay on the user’s device when you use the official client SDK path.
- Users should never type their X Chat PIN into a website that forwards it to a backend.
- Legitimate integrations use the Chat XDK so PIN-backed recovery and crypto run locally (for browsers: WASM + secure key backup).
- A malicious app that obtains private keys can exfiltrate them; there is no server-side “revoke this key” for a pure client-held root key today—design so users never need to hand root keys to you.
What counts as sensitive key material
Choose the right key path by app type
Client apps should prefer secure key backup. Servers and bots often use an exported key blob. Details: Getting Started.
Rules for private keys and PINs
Do
- Collect the PIN only in trusted client UI that feeds the Chat XDK (
unlock/setup) on the same device. - Keep keys in memory only while needed. After unlock, reuse the same Chat instance for the session; call
lock()orfree()on logout. - Zeroize PIN buffers when the API allows (for example pass a
Uint8ArrayPIN in JS so you can clear it afterunlock). - Store bot key blobs in a secret manager (or HSM), encrypt at rest, restrict IAM, rotate process credentials often.
- Log carefully: never log PINs, private keys, key blobs, unwrapped conversation keys, or full secure-backup responses.
- Defend the client: XSS, malicious extensions, and compromised dependencies can read in-memory keys even when the network path is clean.
Do not
- Do not email, screenshot, or ticket a PIN or key blob.
- Do not put private keys or PINs in query strings, analytics, error trackers, or CDN logs.
- Do not ship end-user private keys to “make the backend simpler.”
- Do not confuse OAuth revoke with key revoke. Disconnecting an app does not erase keys a user already exported or typed into a hostile client.
- Do not store raw
export_keysoutput inlocalStorageor unencrypted IndexedDB for production UI apps.
Browser session persistence
Production browser apps should optimize for security first, then UX:
Internal demos sometimes export keys to
localStorage for convenience. That is fine for throwaway prototypes; it is not a production pattern. Prefer:
createChat+unlock(pin)after reload.- Module-level or framework context holding the unlocked instance for SPA navigation.
lock()when the tab logs out or the user locks the app.
OAuth scopes vs encryption keys
These are separate control planes:- OAuth authorizes API calls (list conversations, post ciphertext, fetch events).
- Private keys authorize cryptographic access to message contents.
- Request only the DM scopes it needs.
- Explain why DM access is required.
- Run crypto on device so OAuth never becomes a channel for collecting PINs.
- Stop holding tokens on logout; separately
lock()chat keys.
Operational checklist
- No PIN or private key fields on server request bodies for UI flows
- Secure key backup (
setup/unlock) for clients; secret manager for bot blobs - Unlocked Chat instance scoped to one user session; cleared on logout
- Logging and APM scrubbed of secrets
- CSP and XSS controls on any page that can unlock chat
- User-facing copy: never share PIN with third parties
- Incident plan: if a bot blob leaks, rotate keys / re-register and treat historical ciphertext as exposed to the holder of the old key
Related reading
- Building UI apps with WASM — client architecture
- Cryptography primer — identity keys, conversation keys, secure key backup
- Getting Started — register keys and send a message
- Chat XDK — API reference
- Fundamentals: Security — OAuth and API credential hygiene