diff --git a/content/docs/about/security.mdx b/content/docs/about/security.mdx index 94ddde5..0474dee 100644 --- a/content/docs/about/security.mdx +++ b/content/docs/about/security.mdx @@ -183,9 +183,9 @@ Until then, a new device starts an MLS conversation with nothing from before it ### What it protects -The words and the files, from the server and from anybody who ends up with its disk. A -stolen backup, a resold drive, a compromised host, an order served on an operator: all -of them get ciphertext. +The words, the files and the reactions, from the server and from anybody who ends up +with its disk. A stolen backup, a resold drive, a compromised host, an order served on an +operator: all of them get ciphertext. Under MLS, every message gets its own key, and the key is deleted once your device has used it — that's forward secrecy. Somebody added to the conversation later has no key @@ -210,14 +210,23 @@ decrypted it. So each device keeps what it's read: - the web client keeps it in the browser's storage — clear that browser's site data and it's gone, with no copy anywhere else yet +### Reporting a message + +The server can't read an MLS DM, so it can't show a moderator the message you're +reporting. Your app sends its own copy of it instead, and the server passes that on +marked as the reporter's copy, unverified. It checks that you're both in the +conversation and that it isn't your own message. It can't check that the words are the +ones that were sent, and the moderator is told that. Files in the message aren't sent. + +Each MLS message is signed by the sender's device, so a report could one day come with +proof of who wrote it. That's planned, not built. + ### Not covered yet MLS DMs are the first piece. Still to come: - **Group DMs and private channels.** Both are planned; neither is encrypted this way yet, and a private channel today is just a channel with a smaller member list. -- **Reactions and reports on an MLS message.** The server has no copy of the message to - attach a reaction to, or to hand a moderator. - **Link previews**, which are off in an MLS DM. Making one today means telling the server the link. diff --git a/content/docs/build/server-api.mdx b/content/docs/build/server-api.mdx index ecef5af..042f550 100644 --- a/content/docs/build/server-api.mdx +++ b/content/docs/build/server-api.mdx @@ -217,7 +217,7 @@ Refusals come back on `server:error` with a reason: `invite_required`, | `chat:send` | client → server | | | `chat:fetch` | client → server | | | `chat:edit`, `chat:delete`, `chat:react` | client → server | | -| `chat:report` | client → server | | +| `chat:report` | client → server | `{ accessToken, conversationId, messageId, mls? }`. `mls` is for an MLS message, see [Reports](#reports) | | `chat:typing`, `chat:stop_typing` | both | | | `chat:new`, `chat:edited`, `chat:deleted`, `chat:reaction` | server → client | | | `chat:history` | server → client | Answer to `chat:fetch` | @@ -254,8 +254,9 @@ It never gets a key. The only parts of a message it reads are the group id, the epoch and what kind of message it is. Only one-to-one DMs use it so far. A server that has it says so in `server:info`, as -`mls: { version, ciphersuites, retentionDays }`. A client shouldn't use any of -this on a server that doesn't. +`mls: { version, ciphersuites, retentionDays, reports }`. A client shouldn't use +any of this on a server that doesn't. `reports` is `true` once the server takes a +[report](#reports) of an MLS message. Every request takes `accessToken` and answers through the Socket.IO acknowledgement, with `{ ok: true, ... }` or `{ ok: false, error, message }`. @@ -342,6 +343,32 @@ alone. They go when the log entry goes: after the retention window, or with the conversation. Then any of them nothing else holds gets deleted, the same as a deleted message's files. +#### Reports + +The server never has an MLS message, so it can't hand a moderator the one being +reported. The reporter's app sends its own copy instead, as `mls` on +`chat:report`: + +```ts +mls: { senderServerUserId: string; text: string } +``` + +`messageId` is the id inside the message. The server can't check the copy, so it +goes into the queue marked `unverified: true` in `reports:list`, and a client +should say so on the card. It does check that: + +- the reporter and `senderServerUserId` are both in the conversation, and aren't + the same person. Anything else gets "Message not found", the same answer as a + conversation that doesn't exist +- the reporter has `report_messages`, and hasn't already reported that message +- `text` is at most 32,000 characters and `messageId` at most 64 + +It's rate-limited like any other report. Files in the message aren't sent. + +Resolving one with `delete` closes the report and deletes nothing, since there's +nothing on the server to delete. `delete_all_and_ban` still deletes what the +sender has on the server and bans them. + #### Apps from before MLS An app from before MLS doesn't know `mls:message`. So for every application