What the server keeps.
Field by field: what is stored, what is sealed with the message, and the requests the app does not make on your behalf.
What reaches our servers
Delivery needs to know where a message is going and when it arrived. Everything else is sealed with it.
What you wrote
the flight lands at 6:40, terminal 2
What we store
8f2a41c7d9e0b35a6114fc820d7e93a15c4b6802ff31d95e7a08c264b19d3f7e…
There is no readable copy.
- Message body
- Ciphertext only
- Attachments
- Ciphertext, each file under its own key
- Who it is for
- Conversation and sender, so it can be delivered
- When it was sent
- Timestamp, and whether it was edited
- Voice note
- Length only. The audio, transcript and waveform are sealed
- Shared post
- That it is a post. Author, preview and target are sealed
- Location, contact card, sticker
- Sealed in full
- Poll
- Vote counts. The options themselves are sealed
Sealing works from an allowlist: a field is sealed because it is recognised as one that should be. A field a future version adds and does not recognise stays readable until it joins that list.
Features that switch themselves off
These stop working in an encrypted chat, because making them work would send something readable.
Link previews are not fetched
Pasting a URL into an unencrypted chat asks our server to fetch a preview. In an encrypted conversation that request is not made, because it would tell the server what you were about to send.
Translation is not offered
Translate-before-send is not available in an encrypted chat. It would have to upload what you typed and would leave a translation behind.
Your query stays on the device
Searching runs against the copy your device already decrypted. Jumping to a result asks the server for a message by id. Your query is never part of a request.
Photos lose their location
Every image is re-encoded in the browser before it is encrypted. That step discards the EXIF block, which is where the camera writes GPS coordinates.
The same rule covers the parts of Surf that read a conversation. Catch-up summaries, task extraction and poll suggestions check for an encrypted message first and disable themselves for the whole thread if they find one. A fully encrypted conversation produces no server-side derived data.
Translating a message and transcribing a voice note are the exceptions that prove the rule. Both would have to send readable text, so both were built to run in the browser instead. The model works on your device and the result never reaches us. Transcription happens before the message is sealed, which is why the transcript travels sealed with it.
The one thing a game gives away
A shared board needs a referee, and that is the server.
A live game inside a chat is not end-to-end encrypted, and it is the only thing in a conversation that is not. Two people cannot share a board unless something decides who moved and in what order, so the server does, and it sees the moves. It does not see the rules or a board: a match is a seed, a party size and a list of moves, and no board is ever stored.
Your messages in that same conversation are untouched by this. They stay encrypted, and starting a game does not weaken them.
On the device, at rest
What the browser writes down, and how.
The decrypted copy is sealed too
The readable history your device keeps is itself encrypted, under a key marked non-extractable, so a copy of the browser's storage files yields ciphertext and a key that cannot be exported. If a device has no usable keystore, the history is held in memory for the session and never written to disk.
An undelivered message is sealed as well
Text that could not be delivered survives a reload so you can send it again. It is written into the same sealed store, and dropped when you switch accounts.
Who can see that you are there
The signals around a message, and what each one tells the other side.
- Typing indicator
- A live presence channel. Nothing is written to any database
- Read receipts
- Reciprocal: turning them off retracts the ones you sent and stops you seeing others'
- Turning receipts off
- A Sapphire setting. Free accounts send them
- Unread badge
- Separate from receipts, and never shared with the other side
- Blocking
- Silent. Nothing is sent to the person blocked
If someone demands your messages
What we are able to hand over.
What we hold is the encrypted message and the routing that delivered it: which conversation, which sender, when. A lawful demand gets that. It does not get a readable message, because we do not have one and hold no key that produces one. The same is true of a backup: the server holds a wrapped key it cannot unwrap.
Deleting your account schedules a teardown thirty days out, and it can be cancelled inside that window. The in-app data export covers your profile, posts, bookmarks and follows. It does not include your messages, because our servers cannot read them.
Surf's privacy policy covers the account this signs you in with.
What we have not done
There is no third-party audit of this implementation and the source is not published. The primitives are standard and listed on the security page.