ciphermesh 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,57 @@
3
3
  Notable changes per release. Older versions are reconstructed from the git
4
4
  history — the commit bodies and pull requests remain the fuller record.
5
5
 
6
+ ## 2.10.0
7
+
8
+ ### Added
9
+
10
+ - **\`--check\` for operators.** The deploy guide already explained every
11
+ footgun here, which is the problem: a document is read once, by whoever set
12
+ the machine up, while the misconfiguration lasts as long as the machine does.
13
+ The worst of them — \`TRUST_PROXY\` left off behind a reverse proxy — is
14
+ invisible from outside, because everything works and the per-IP cap, the rate
15
+ limit and the banlist simply apply to the proxy and protect nobody.
16
+
17
+ \`ciphermesh-server --check\` validates and exits without opening a socket, so
18
+ it is safe against a live host, and exits non-zero on an error so a deploy
19
+ script can gate on it. The same findings print at every startup, because a
20
+ warning you have to ask for is a warning nobody sees.
21
+
22
+ - **Connection-rate limiting.** The relay capped how many sockets one address
23
+ could hold, and how many messages a session could send, but nothing capped how
24
+ fast an address could _open_ connections. Connect, run the hybrid handshake,
25
+ disconnect, repeat: the concurrency cap never trips because the sockets are
26
+ never held, and every attempt costs the relay an X25519 and an ML-KEM-768
27
+ operation while costing the client almost nothing. That asymmetry was the one
28
+ real denial-of-service route into a public hub.
29
+
30
+ An address that exceeds `CONNECTION_RATE_PER_MINUTE` (60 by default, still
31
+ LAN-friendly) is refused for a minute, then five, then thirty. An hour of
32
+ behaving clears the record, so a shared NAT gateway cannot accumulate strikes
33
+ forever, and the refusal says how long to wait so a well-behaved client backs
34
+ off instead of extending its own ban.
35
+
36
+ - **A byte budget per connection** (`MAX_BYTES_PER_SECOND`,
37
+ `MAX_BYTES_BURST`). The message limit counts messages, and messages are padded
38
+ into buckets of up to 32 KiB, so a session sitting at the limit is a
39
+ multi-megabit stream. Bytes are the resource that runs out. The burst
40
+ allowance keeps a legitimate file transfer from looking like an attack.
41
+
42
+ Both are continuously refilling token buckets rather than fixed windows: a
43
+ fixed window lets a caller spend its whole allowance at the end of one window
44
+ and again at the start of the next, which is twice the intended rate at
45
+ exactly the moment an attacker aims for.
46
+
47
+ ## 2.9.0
48
+
49
+ ### Added
50
+
51
+ - `/block` now works in **P2P** as well. It matters more there than on a relay:
52
+ P2P has no room owners at all, so `/kick`, `/mute` and `/ban` have nobody to
53
+ act on anyone's behalf. Refusing to listen is the only protection there is, and
54
+ the refusal message for the moderation commands now says so instead of leaving
55
+ the user with nothing.
56
+
6
57
  ## 2.8.0
7
58
 
8
59
  ### Added
package/README.md CHANGED
@@ -41,28 +41,28 @@ forwarding, survives CGNAT).
41
41
 
42
42
  ## ✨ Highlights
43
43
 
44
- | | Feature | The gist |
45
- |-----|---------|----------|
46
- | 🔐 | **Real E2EE** | Curve25519 + XSalsa20-Poly1305 via libsodium, keys in `sodium_malloc` — never touch disk |
47
- | 🔄 | **Perfect Forward Secrecy** | Double Ratchet: one key per message, compromise today ≠ read yesterday |
48
- | 🛡️ | **Hybrid post-quantum** | X25519 **+ ML-KEM-768** folded into the ratchet root — beats "harvest now, decrypt later" while staying ≥ classical security ([details](docs/ARCHITECTURE.md)) |
49
- | 🕶️ | **Metadata resistance** | **Sealed sender** — the relay never sees who sent a message — plus fixed-bucket length padding on every ciphertext and opt-in cover traffic (`/cover`) |
50
- | 🕵️ | **TOFU + SAS** | Key-change detection (MITM alarm), 6-digit voice-verifiable codes, and inline **✓/✗** trust badges next to names |
51
- | 🌐 | **LAN & internet** | Auto-detects Tailscale, shows the reachable address in the banner |
52
- | 📨 | **Invites with QR** | `/invite` prints a `ciphermesh://` string + QR — paste it, you're in the right room |
53
- | ✓✓ | **Encrypted read receipts** | The ✓✓ travels as ordinary ciphertext — the server can't tell it apart |
54
- | 🗂️ | **Encrypted local history** | Opt-in (passphrase only), Argon2id + XSalsa20-Poly1305, `/search` & `/export` |
55
- | 🖼️ | **Image previews** | Received photos render right in the chat as colored half-blocks |
56
- | 📎 | **Resumable transfers** | Lost chunks are re-requested; reconnects resume from where they stopped |
57
- | 💬 | **Modern chat feel** | Right-aligned own messages, per-user emoji avatars, replies with quotes, `:fire:` → 🔥 |
58
- | 🎞️ | **Animated UI** | Splash intro, reconnect spinner, live transfer bars (shimmer + ETA), a lock-closing handshake on connect, and a pulsing "new messages ↓" pill |
59
- | 👻 | **Deniable & ephemeral** | Symmetric-crypto deniable mode; ephemeral messages *burn away* char-by-char when they expire |
60
- | 🔒 | **Private rooms** | `/create <room> <password>` — zero-knowledge: the password never leaves your machine (Argon2id → Ed25519 challenge-response) and room content gets an extra symmetric layer the relay can't fake its way into |
61
- | 🗂️ | **Multi-room buffers** | Be in several rooms at once — **Alt+1..9** switches, unread badges per room. Which room a message belongs to travels *inside* the encrypted payload: the relay never learns it |
62
- | 🩺 | **It explains itself** | `/doctor` diagnoses a failing connection layer by layer — address, DNS, TCP, TLS, protocol — and tells you what to do about each failure |
63
- | 🔐 | **Screen lock** | `/lock` and `/autolock` put the session behind your passphrase when you step away; `/panic` is still there for the worse moment |
64
- | 🛰️ | **Serverless P2P mode** | mDNS peer discovery on the LAN — no relay at all, and nearly the same command set |
65
- | 🧩 | **Plugins** | Drop a JS file in `~/.ciphermesh/plugins` and get new slash-commands — `/roll` and `/poll` examples included ([Plugin API](docs/PLUGINS.md)) |
44
+ | | Feature | The gist |
45
+ | --- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | 🔐 | **Real E2EE** | Curve25519 + XSalsa20-Poly1305 via libsodium, keys in `sodium_malloc` — never touch disk |
47
+ | 🔄 | **Perfect Forward Secrecy** | Double Ratchet: one key per message, compromise today ≠ read yesterday |
48
+ | 🛡️ | **Hybrid post-quantum** | X25519 **+ ML-KEM-768** folded into the ratchet root — beats "harvest now, decrypt later" while staying ≥ classical security ([details](docs/ARCHITECTURE.md)) |
49
+ | 🕶️ | **Metadata resistance** | **Sealed sender** — the relay never sees who sent a message — plus fixed-bucket length padding on every ciphertext and opt-in cover traffic (`/cover`) |
50
+ | 🕵️ | **TOFU + SAS** | Key-change detection (MITM alarm), 6-digit voice-verifiable codes, and inline **✓/✗** trust badges next to names |
51
+ | 🌐 | **LAN & internet** | Auto-detects Tailscale, shows the reachable address in the banner |
52
+ | 📨 | **Invites with QR** | `/invite` prints a `ciphermesh://` string + QR — paste it, you're in the right room |
53
+ | ✓✓ | **Encrypted read receipts** | The ✓✓ travels as ordinary ciphertext — the server can't tell it apart |
54
+ | 🗂️ | **Encrypted local history** | Opt-in (passphrase only), Argon2id + XSalsa20-Poly1305, `/search` & `/export` |
55
+ | 🖼️ | **Image previews** | Received photos render right in the chat as colored half-blocks |
56
+ | 📎 | **Resumable transfers** | Lost chunks are re-requested; reconnects resume from where they stopped |
57
+ | 💬 | **Modern chat feel** | Right-aligned own messages, per-user emoji avatars, replies with quotes, `:fire:` → 🔥 |
58
+ | 🎞️ | **Animated UI** | Splash intro, reconnect spinner, live transfer bars (shimmer + ETA), a lock-closing handshake on connect, and a pulsing "new messages ↓" pill |
59
+ | 👻 | **Deniable & ephemeral** | Symmetric-crypto deniable mode; ephemeral messages _burn away_ char-by-char when they expire |
60
+ | 🔒 | **Private rooms** | `/create <room> <password>` — zero-knowledge: the password never leaves your machine (Argon2id → Ed25519 challenge-response) and room content gets an extra symmetric layer the relay can't fake its way into |
61
+ | 🗂️ | **Multi-room buffers** | Be in several rooms at once — **Alt+1..9** switches, unread badges per room. Which room a message belongs to travels _inside_ the encrypted payload: the relay never learns it |
62
+ | 🩺 | **It explains itself** | `/doctor` diagnoses a failing connection layer by layer — address, DNS, TCP, TLS, protocol — and tells you what to do about each failure |
63
+ | 🔐 | **Screen lock** | `/lock` and `/autolock` put the session behind your passphrase when you step away; `/panic` is still there for the worse moment |
64
+ | 🛰️ | **Serverless P2P mode** | mDNS peer discovery on the LAN — no relay at all, and nearly the same command set |
65
+ | 🧩 | **Plugins** | Drop a JS file in `~/.ciphermesh/plugins` and get new slash-commands — `/roll` and `/poll` examples included ([Plugin API](docs/PLUGINS.md)) |
66
66
 
67
67
  ## 🚀 Quick start
68
68
 
@@ -131,10 +131,10 @@ download from the
131
131
  > v2.7.2 never embedded its native addon and only ran on the machine that built
132
132
  > it, so those attachments have been removed.
133
133
 
134
- | Binary | What it is |
135
- |---|---|
136
- | `ciphermesh-<platform>` | Everything: client, relay and P2P. `ciphermesh server` and `ciphermesh p2p` work exactly as they do on npm. |
137
- | `ciphermesh-server-<platform>` | Relay only, for self-hosters who want nothing else on the machine. |
134
+ | Binary | What it is |
135
+ | ------------------------------ | ----------------------------------------------------------------------------------------------------------- |
136
+ | `ciphermesh-<platform>` | Everything: client, relay and P2P. `ciphermesh server` and `ciphermesh p2p` work exactly as they do on npm. |
137
+ | `ciphermesh-server-<platform>` | Relay only, for self-hosters who want nothing else on the machine. |
138
138
 
139
139
  **Everyone** (including the host):
140
140
 
@@ -200,39 +200,41 @@ software was built for.
200
200
  <details>
201
201
  <summary><b>Essentials</b></summary>
202
202
 
203
- | Command | Description |
204
- |---------|-------------|
205
- | `/help` | All commands |
206
- | `/tips` | Show a rotating security/UX tip |
207
- | `/users` | Who's online (with away/status) |
208
- | `/msg <nick> <text>` | Private message (DM) |
209
- | `/reply <text>` | Reply quoting the last received message |
210
- | `/me <action>` | Third-person action *«felipe is compiling»* |
211
- | `/watch [add\|remove\|clear]` | Alert on a keyword in **any** room, like a mention |
212
- | `/invite [host:port]` | Generate a `ciphermesh://` invite + QR code |
213
- | `/nick <new>` | Change nickname (before joining recovers from "nickname taken") |
214
- | `/quit` | Leave |
203
+ | Command | Description |
204
+ | ----------------------------- | ----------------------------------------------------------------- |
205
+ | `/help` | All commands |
206
+ | `/tips` | Show a rotating security/UX tip |
207
+ | `/plugins` | List loaded plugins and the commands they add |
208
+ | `/users` | Who's online (with away/status) |
209
+ | `/msg <nick> <text>` | Private message (DM) |
210
+ | `/reply <text>` | Reply quoting the last received message |
211
+ | `/me <action>` | Third-person action _«ana is compiling»_ |
212
+ | `/watch [add\|remove\|clear]` | Alert on a keyword in **any** room, like a mention |
213
+ | `/invite [host:port]` | Generate a `ciphermesh://` invite + QR code |
214
+ | `/nick <new>` | Change nickname (before joining — recovers from "nickname taken") |
215
+ | `/quit` | Leave |
215
216
 
216
217
  </details>
217
218
 
218
219
  <details>
219
220
  <summary><b>Rooms</b></summary>
220
221
 
221
- | Command | Description |
222
- |---------|-------------|
223
- | `/join <room> [password]` | Open a room as a **new buffer** — you stay in your other rooms (IRC style) |
224
- | `/leave [room]` | Leave a room; its buffer closes (the last room is protected) |
225
- | `/create <room> <password>` | Create a **private room** 🔒 — see below |
226
- | `/rooms` | List rooms (🔒 marks private ones) |
227
- | `/room` | Current room + your buffer list |
228
- | `/topic [text\|clear]` | Show or set the room topic — shown in the status bar and synced to whoever joins later |
229
- | `/owner` | Room owner |
230
- | `/kick` `/mute` `/ban` | Owner moderation |
222
+ | Command | Description |
223
+ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
+ | `/join <room> [password]` | Open a room as a **new buffer** — you stay in your other rooms (IRC style) |
225
+ | `/leave [room]` | Leave a room; its buffer closes (the last room is protected) |
226
+ | `/create <room> <password>` | Create a **private room** 🔒 — see below |
227
+ | `/rooms` | List rooms (🔒 marks private ones) |
228
+ | `/room` | Current room + your buffer list |
229
+ | `/topic [text\|clear]` | Show or set the room topic — shown in the status bar and synced to whoever joins later |
230
+ | `/owner` | Room owner |
231
+ | `/kick` `/mute` `/ban` | Owner moderation — bound to the public key, so a rename does not undo a ban |
232
+ | `/block` `/unblock` `/blocklist` | Stop seeing someone, **just for you**. Nothing is sent, the relay never learns, and they are not told — so anyone can use it, including in `general`, which has no owner. Works in P2P too, where there is no moderation at all. |
231
233
 
232
234
  **Buffers:** be in several rooms at once — **Alt+1..9** switches, and the status
233
235
  bar shows `[1:general] [2:dev •3]` with per-room unread badges. Because the
234
236
  relay is blind (sealed sender), which room a message belongs to travels
235
- *inside* the encrypted payload — the server never learns it.
237
+ _inside_ the encrypted payload — the server never learns it.
236
238
 
237
239
  **Private rooms** are zero-knowledge: the password never leaves your machine.
238
240
  Joining derives an Ed25519 key from the password (Argon2id) and answers a
@@ -247,22 +249,22 @@ without verifying couldn't read a word. Share the password out-of-band.
247
249
  <details>
248
250
  <summary><b>Trust & security</b></summary>
249
251
 
250
- | Command | Description |
251
- |---------|-------------|
252
- | `/fingerprint [nick]` | Key fingerprint + a deterministic **randomart** picture of the key |
253
- | `/verify <nick>` | SAS code (~40-bit) + QR + key randomart for out-of-band verification |
254
- | `/verify-confirm <nick>` | Mark peer as verified |
255
- | `/trust <nick>` / `/trustlist` | Accept new key / trust status |
256
- | `/contacts [add\|remove\|all]` | Contact book — persistent aliases on trust records ("this fingerprint is João"); shows in `/users`, rides along in identity backups |
257
- | `/backup [path]` | Encrypted backup of identity + verified peers (restore at startup) |
258
- | `/deniable [on\|off]` | Plausible-deniability mode |
252
+ | Command | Description |
253
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
254
+ | `/fingerprint [nick]` | Key fingerprint + a deterministic **randomart** picture of the key |
255
+ | `/verify <nick>` | SAS code (~40-bit) + QR + key randomart for out-of-band verification |
256
+ | `/verify-confirm <nick>` | Mark peer as verified |
257
+ | `/trust <nick>` / `/trustlist` | Accept new key / trust status |
258
+ | `/contacts [add\|remove\|all]` | Contact book — persistent aliases on trust records ("this fingerprint is João"); shows in `/users`, rides along in identity backups |
259
+ | `/backup [path]` | Encrypted backup of identity + verified peers (restore at startup) |
260
+ | `/deniable [on\|off]` | Plausible-deniability mode |
259
261
  | `/lock` / `/autolock <min\|off>` | Lock the screen behind the session passphrase — manually or after idle time (`autoLock` in config). Privacy for the "stepped away" moment; `/panic` is for the worst one |
260
- | `/panic [yes]` | Duress wipe — securely erase all on-disk secrets (session, history, trust, keys) and exit |
261
- | `/cover [on\|constant\|off]` | Cover traffic — `on` = jittered decoys, `constant` = steady-rate paced channel |
262
- | `/theme [name]` | Nick colour theme: neon, matrix, mono, sunset, ocean |
263
- | `/ephemeral <30s\|5m\|1h\|off>` | Self-destructing messages |
264
- | `/receipts [on\|off]` | Send read receipts (✓✓) |
265
- | `/audit [n]` | Local audit log |
262
+ | `/panic [yes]` | Duress wipe — securely erase all on-disk secrets (session, history, trust, keys) and exit |
263
+ | `/cover [on\|constant\|off]` | Cover traffic — `on` = jittered decoys, `constant` = steady-rate paced channel |
264
+ | `/theme [name]` | Nick colour theme: neon, matrix, mono, sunset, ocean |
265
+ | `/ephemeral <30s\|5m\|1h\|off>` | Self-destructing messages |
266
+ | `/receipts [on\|off]` | Send read receipts (✓✓) |
267
+ | `/audit [n]` | Local audit log |
266
268
 
267
269
  A green **✓** next to a name marks a SAS-verified peer; a red **✗** flags a key that changed since you last saw it (possible MITM). A newly-arrived unverified peer triggers a one-time reminder to `/verify` them.
268
270
 
@@ -271,40 +273,41 @@ A green **✓** next to a name marks a SAS-verified peer; a red **✗** flags a
271
273
  <details>
272
274
  <summary><b>History & files</b></summary>
273
275
 
274
- | Command | Description |
275
- |---------|-------------|
276
- | `/file <path>` | Offer a file (≤ 50MB) — the recipient must `/accept`; transfers resume |
277
- | `/voice [secs]` | Record & send an encrypted voice note (needs `sox`/`ffmpeg`; default 10s) |
278
- | `/play [path]` | Play the last received voice note (`afplay`/`sox`/`ffplay`) |
279
- | `/accept [id]` / `/reject [id]` | Accept / decline an incoming file offer |
280
- | `/img [path]` | Render the last received image in **full resolution** (kitty/iTerm2) |
281
- | `/search <term>` | Search the encrypted local history (on disk, across sessions) |
282
- | `/find [term]` — **Ctrl+F** | Search **this room's scrollback** and press Enter to **jump to the message**, highlighted |
283
- | `/doctor [host:port]` | Diagnose why a connection fails: address, DNS, TCP port, TLS (CA vs self-signed) and protocol version — each failure with what to do about it |
284
- | `/history [n]` | Last n messages from history |
285
- | `/retention <7d\|24h\|30m>` | Purge local history older than the given age |
286
- | `/export [path]` | Export history as .txt or .json (plaintext!) |
276
+ | Command | Description |
277
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
278
+ | `/file <path>` | Offer a file (≤ 50MB) — the recipient must `/accept`; transfers resume |
279
+ | `/voice [secs]` | Record & send an encrypted voice note (needs `sox`/`ffmpeg`; default 10s) |
280
+ | `/play [path]` | Play the last received voice note (`afplay`/`sox`/`ffplay`) |
281
+ | `/accept [id]` / `/reject [id]` | Accept / decline an incoming file offer |
282
+ | `/img [path]` | Render the last received image in **full resolution** (kitty/iTerm2) |
283
+ | `/search <term>` | Search the encrypted local history (on disk, across sessions) |
284
+ | `/find [term]` — **Ctrl+F** | Search **this room's scrollback** and press Enter to **jump to the message**, highlighted |
285
+ | `/doctor [host:port]` | Diagnose why a connection fails: address, DNS, TCP port, TLS (CA vs self-signed) and protocol version — each failure with what to do about it |
286
+ | `/history [n]` | Last n messages from history |
287
+ | `/retention <7d\|24h\|30m>` | Purge local history older than the given age |
288
+ | `/export [path]` | Export history as .txt or .json (plaintext!) |
287
289
 
288
290
  </details>
289
291
 
290
292
  <details>
291
293
  <summary><b>Presence & fun</b></summary>
292
294
 
293
- | Command | Description |
294
- |---------|-------------|
295
- | `/away [reason]` / `/back` | Mark yourself away — while away, unreads are counted (`[away · N new]`) and `/back` shows a summary |
296
- | `/mentions [n]` | Recent mentions of you this session (who, where, when) |
297
- | `/status <text\|off>` | Free-form status emojis welcome (`/status :fire: coding`) |
298
- | `/react <emoji>` | React to the last message the emoji lands **on the message**, with a count when several people react |
299
- | `/edit` `/delete` | Edit or delete your last message — the **original line is rewritten in place** (marked *(edited)*) or replaced by a tombstone, instead of a new line you have to mentally staple to it |
300
- | `/pin` `/unpin` `/pins` | Pin messages |
301
- | `/sound` `/notify` | Sound / desktop notifications |
302
- | `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Do-not-disturb, mentions-only, or quiet hours |
303
- | `/clear` | Clear the chat |
295
+ | Command | Description |
296
+ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
297
+ | `/away [reason]` / `/back` | Mark yourself away — while away, unreads are counted (`[away · N new]`) and `/back` shows a summary |
298
+ | `/autoaway <minutes\|off>` | Go away by itself after that long without typing (1–240 minutes) |
299
+ | `/mentions [n]` | Recent mentions of you this session (who, where, when) |
300
+ | `/status <text\|off>` | Free-form statusemojis welcome (`/status :fire: coding`) |
301
+ | `/react <emoji>` | React to the last message — the emoji lands **on the message**, with a count when several people react |
302
+ | `/edit` `/delete` | Edit or delete your last message — the **original line is rewritten in place** (marked _(edited)_) or replaced by a tombstone, instead of a new line you have to mentally staple to it |
303
+ | `/pin` `/unpin` `/pins` | Pin messages |
304
+ | `/sound` `/notify` | Sound / desktop notifications |
305
+ | `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Do-not-disturb, mentions-only, or quiet hours |
306
+ | `/clear` | Clear the chat |
304
307
 
305
308
  </details>
306
309
 
307
- Typing `:fire:` anywhere becomes 🔥 (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Alt+Enter** (or Shift+Enter where the terminal supports it, plus Ctrl+J) inserts a newline for multi-line messages; Enter sends. Pasting multi-line text (code included) keeps its line breaks — paste, check, Enter. Markdown works: \`code\`, **bold**, *italic*, links, plus fenced \`\`\` code blocks and | tables |. Received images preview inline (half-blocks) and render full-res with `/img` on kitty/iTerm2. Day separators and message grouping keep the log clean.
310
+ Typing `:fire:` anywhere becomes 🔥 (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Alt+Enter** (or Shift+Enter where the terminal supports it, plus Ctrl+J) inserts a newline for multi-line messages; Enter sends. Pasting multi-line text (code included) keeps its line breaks — paste, check, Enter. Markdown works: \`code\`, **bold**, _italic_, links, plus fenced \`\`\` code blocks and | tables |. Received images preview inline (half-blocks) and render full-res with `/img` on kitty/iTerm2. Day separators and message grouping keep the log clean.
308
311
 
309
312
  ### First run & config file
310
313
 
@@ -324,7 +327,7 @@ All keys are optional (unknown keys are ignored):
324
327
 
325
328
  ```json
326
329
  {
327
- "nickname": "felipe",
330
+ "nickname": "ana",
328
331
  "server": "wss://100.x.y.z:3600",
329
332
  "sound": false,
330
333
  "notify": true,
@@ -360,7 +363,7 @@ All keys are optional (unknown keys are ignored):
360
363
  **Argon2id + XSalsa20-Poly1305** — no passphrase, no persistence.
361
364
  - **Hybrid post-quantum**: each pairwise session mixes an ML-KEM-768 secret
362
365
  into the ratchet root at setup, so recorded traffic stays unreadable to a
363
- future quantum adversary. It is *added* to X25519, never replaces it —
366
+ future quantum adversary. It is _added_ to X25519, never replaces it —
364
367
  security is at least the classical one. `/trustlist` shows `[PQ]`.
365
368
  - **Private rooms** never send the password anywhere: it derives an Ed25519
366
369
  key (Argon2id) that answers a server challenge, and the room content carries