ciphermesh 2.12.0 → 2.14.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,193 @@
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.14.0
7
+
8
+ **The chat, read properly.** Three things a user reported in the same session:
9
+ notifications wrecking the screen, Shift+Enter sending instead of breaking a
10
+ line, and long messages running the whole width of the terminal.
11
+
12
+ ### Changed
13
+
14
+ - **Messages are laid out as blocks.** A header naming the sender, then the text
15
+ wrapped at 65 % of the window (78 columns at most) and indented under it,
16
+ instead of one line handed to the terminal's own wrapping and left to run into
17
+ the border. Own messages are no longer right-aligned — everything starts at the
18
+ same column and a coloured rule down the left says what a message is: yellow
19
+ when it mentions you, magenta for a DM, the accent for your own.
20
+
21
+ Runs from one sender fold under a single header, but only inside the same
22
+ minute, so folding a run never costs you a timestamp. Notices — system, error,
23
+ `/me`, tombstones — share the gutter and wrap with a hanging indent, though at
24
+ the window's width rather than the reading width, so `/help`'s table is not
25
+ folded in half on a window with room to spare.
26
+
27
+ Everything is laid out again when the terminal is resized, stored room buffers
28
+ included, so the scrollback is never left measured for a window you no longer
29
+ have.
30
+
31
+ ### Added
32
+
33
+ - **Shift+Enter inserts a newline.** A terminal cannot tell it from Enter unless
34
+ asked, so CipherMesh now negotiates the kitty keyboard protocol and xterm's
35
+ `modifyOtherKeys` on startup and undoes both on the way out. What the terminal
36
+ reports back is decoded ahead of the UI's key parser, which could not read it —
37
+ and without that step would have typed `13;2u` into the composer.
38
+
39
+ **Alt+Enter was broken too** and works now, on every terminal, protocol or not;
40
+ Ctrl+J still does the same. `CIPHERMESH_LEGACY_KEYS=1` turns the negotiation off
41
+ for terminals that dislike it.
42
+
43
+ ### Fixed
44
+
45
+ - **Desktop notifications no longer wreck the chat on Windows.** SnoreToast, the
46
+ back-end behind `node-notifier`, ignores the pipes it is given when
47
+ notifications are disabled for the application and writes its diagnostics to
48
+ the attached console instead — the one the chat is drawn on. A room could
49
+ become unreadable, one burst per incoming message, with `/notify off` the only
50
+ way out.
51
+
52
+ Notifications are now delivered on Windows by a detached helper with no console
53
+ of its own, so nothing it prints can reach the terminal. The first refusal also
54
+ mutes them for the session and says so once, in one line with the reason
55
+ summarised rather than the raw command line; sound alerts keep working and
56
+ `/notify on` retries. And they are rate-limited to one per three seconds, which
57
+ was the other half of the complaint.
58
+
59
+ ### Internal
60
+
61
+ - **`docs/ARCHITECTURE.md` matches the code again, and a test keeps it that
62
+ way.** It listed `play-sound` as a production dependency, which it is not, and
63
+ omitted seven of the eleven that are; its directory tree named files that do
64
+ not exist and 2 of the 27 modules under `src/shared/`; about half the document
65
+ was still Portuguese; and it called the project SecureLAN Chat, three renames
66
+ later. `test/architecture-doc.test.js` now fails on any of those — the same
67
+ shape as the command-list check, for the same reason.
68
+
69
+ - The UI can be tested headlessly. `UI` takes the streams blessed drives, so the
70
+ layout — wrapping, alignment, relayout — runs against a fake terminal instead
71
+ of only being checked by eye.
72
+
73
+ - eslint 10.9.1, github/codeql-action 4.37.9.
74
+
75
+ ## 2.13.0
76
+
77
+ **Multi-device.** One identity, several devices, and none of them holding a copy
78
+ of the others' secrets.
79
+
80
+ Until now two machines could only share an identity by sharing its private key —
81
+ `/backup` copies the whole thing — after which the relay refused the second
82
+ nickname, each message reached exactly one of them, and a peer verifying both
83
+ was shown the same fingerprint twice and told that was normal. That is replaced.
84
+
85
+ An identity is now an Ed25519 key that only ever signs. Each device has its own
86
+ message key, listed and signed by that identity. Adding a device grants it a
87
+ signed place on the list; it never receives the identity secret, so a stolen
88
+ phone is a stolen phone rather than a stolen identity, and only the device
89
+ holding the secret can add or remove.
90
+
91
+ `/room` also learned to say how the room is sending, and why.
92
+
93
+ ### Added
94
+
95
+ - **`/device`: one identity, several devices.** A second device asks with
96
+ `/device request`, the device holding the identity key answers with
97
+ `/device add`, and the new one takes it with `/device accept`. Two short
98
+ strings, small enough for a QR code, and neither of them secret — a request is
99
+ a public key and a grant is a signed statement that goes to every peer anyway.
100
+
101
+ The identity secret never moves. A secondary can prove which identity it
102
+ belongs to and can publish that proof, but it cannot sign a new list, so
103
+ adding and removing stay with one device. Two costs come with that and are
104
+ worth knowing: losing that device means no more adding or removing, and a
105
+ secondary does not rotate its message key, because it could not re-sign the
106
+ list that names it.
107
+
108
+ `/device remove` signs a shorter list **and rotates the room**. A removed
109
+ device still holds every member's sender chain, and a chain ratchets forward —
110
+ dropping it from a list stops the relay delivering to it and does not stop it
111
+ reading. It keeps what it already received; that is what forward secrecy
112
+ means.
113
+
114
+ - **Verification follows the identity, not the device.** Once both sides can,
115
+ `/verify` compares identity keys, so adding or rotating a device no longer
116
+ invalidates a verification. The switch is symmetric — both sides make it
117
+ together — so a pair is never shown two different codes, and `/verify` says
118
+ which of the two it is showing. Verifications you already have are carried
119
+ across silently, and only when a signed list names the very key you compared
120
+ digits over.
121
+
122
+ - **Another of someone's devices is not "their key changed".** It still warns
123
+ the first time, because claiming an identity proves nothing on its own. When
124
+ the proof arrives the warning is answered out loud, and that key is quiet from
125
+ then on. Your own other device is recognised as yours: `/users` counts people
126
+ rather than connections, a line you sent from your phone is shown as yours,
127
+ and your own nickname in your own line does not notify you.
128
+
129
+ - **`/room` reports how the room is sending, and why.** One ciphertext for the
130
+ room, or one envelope per member — and when it is the expensive one, the
131
+ reason: an older hub, deniable mode, or the peers by name.
132
+
133
+ ```
134
+ Sending: one ciphertext to the room (sender keys), read by 4
135
+ Sending: 4 envelopes per message — carol is on a build without sender keys
136
+ Sending: 2 envelopes per message — this relay cannot fan out a room-addressed message
137
+ ```
138
+
139
+ An older hub is reported ahead of older peers, because both can be true at
140
+ once and naming peers who are perfectly current sends you after the wrong
141
+ problem. P2P gets the same line for its own reasons: a mesh sends one frame
142
+ per peer either way, so there the saving is *encryptions*, and constant cover
143
+ is a reason to stay pairwise that the relay path does not have.
144
+
145
+ ### Fixed
146
+
147
+ - **A second device no longer reads as an attack.** It arrives under the same
148
+ nickname with a key the trust record has never seen, which is the shape of a
149
+ man-in-the-middle and was reported as one.
150
+
151
+ - **Guarded key memory is released, not merely zeroed.** `KeyManager` zeroed its
152
+ secret keys on `destroy()` and left the pages `mlock`'d until the garbage
153
+ collector happened to run the finaliser. An operating system caps how much a
154
+ process may lock, and the cap is small on Linux and unlimited on macOS — so
155
+ the failure was invisible in development and landed as an abort in whatever
156
+ allocated next. The same pattern was fixed elsewhere in 2.12.0; this is the
157
+ rest of it in that file.
158
+
159
+ ### Internal
160
+
161
+ - **The per-peer send loop is not being retired, and the plan that said it might
162
+ be was wrong.** It was written up as a compatibility shim that ages out once
163
+ everybody upgrades. It is not: it is the pairwise send path, and two of the
164
+ four things that need it need it permanently — `/deniable`, because
165
+ deniability is a property of the pairwise construction, and sender-key
166
+ distribution, because a distribution is authenticated by the envelope it
167
+ arrives in and the group path cannot bootstrap itself. Recorded in
168
+ `docs/design/sender-keys-on-relay.md`.
169
+
170
+ - **A design document for multi-device**, written from the code before any of
171
+ it moved, and kept as written with the decisions recorded in place. It is the
172
+ reason the arc could be built in eight landable steps.
173
+
174
+ - **One nickname may be held by several devices of one identity.** The relay
175
+ admits the second only if its JOIN carries a list signed by the identity the
176
+ name is already using and naming that JOIN's own key. No challenge is issued
177
+ and none is needed: replaying somebody else's list buys a seat in a room whose
178
+ messages you cannot read. The name is released when the last device leaves.
179
+
180
+ - **New capability `dl1`**, the first with no relay half — a device list travels
181
+ on the pairwise channel the relay already carries, so there is nothing for it
182
+ to agree to.
183
+
184
+ - **Multi-device is not coming to the mesh**, and the mesh now says so. A P2P
185
+ peer is keyed by nickname, which is exactly what two of your devices share, so
186
+ it is a different design. `/device`, `/create`, `/invite` and `/nick` explain
187
+ why they need a relay instead of guessing at a typo — `/device` used to
188
+ suggest `/voice`.
189
+
190
+ - Dependency bumps: `@noble/post-quantum` 0.7.0, `eslint` 10.8.1,
191
+ `globals` 17.11.0.
192
+
6
193
  ## 2.12.0
7
194
 
8
195
  Sender keys now send. 2.11.0 shipped the half that reads a group message; this
package/README.md CHANGED
@@ -54,7 +54,7 @@ forwarding, survives CGNAT).
54
54
  | 🗂️ | **Encrypted local history** | Opt-in (passphrase only), Argon2id + XSalsa20-Poly1305, `/search` & `/export` |
55
55
  | 🖼️ | **Image previews** | Received photos render right in the chat as colored half-blocks |
56
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:` → 🔥 |
57
+ | 💬 | **Modern chat feel** | Messages laid out as wrapped blocks with a coloured rule on your own, per-user emoji avatars, replies with quotes, `:fire:` → 🔥 |
58
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
59
  | 👻 | **Deniable & ephemeral** | Symmetric-crypto deniable mode; ephemeral messages _burn away_ char-by-char when they expire |
60
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 |
@@ -225,7 +225,7 @@ software was built for.
225
225
  | `/leave [room]` | Leave a room; its buffer closes (the last room is protected) |
226
226
  | `/create <room> <password>` | Create a **private room** 🔒 — see below |
227
227
  | `/rooms` | List rooms (🔒 marks private ones) |
228
- | `/room` | Current room + your buffer list |
228
+ | `/room` | Current room, how it is sending it, and your buffer list |
229
229
  | `/topic [text\|clear]` | Show or set the room topic — shown in the status bar and synced to whoever joins later |
230
230
  | `/owner` | Room owner |
231
231
  | `/kick` `/mute` `/ban` | Owner moderation — bound to the public key, so a rename does not undo a ban |
@@ -251,12 +251,13 @@ without verifying couldn't read a word. Share the password out-of-band.
251
251
 
252
252
  | Command | Description |
253
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 |
254
+ | `/fingerprint [nick]` | Key fingerprint, the identity fingerprint when there is one, and a deterministic **randomart** picture of the key |
255
+ | `/verify <nick>` | SAS code (~40-bit) + QR + key randomart for out-of-band verification; says whether the code is over identity keys or device keys |
256
256
  | `/verify-confirm <nick>` | Mark peer as verified |
257
257
  | `/trust <nick>` / `/trustlist` | Accept new key / trust status |
258
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
259
  | `/backup [path]` | Encrypted backup of identity + verified peers (restore at startup) |
260
+ | `/device [list\|request\|add\|accept\|remove]` | Your devices under one identity. The identity key never leaves the device that holds it, so a second device is granted a signed place on the list rather than a copy of your identity. Removing one rotates the room, so nothing said afterwards reaches it |
260
261
  | `/deniable [on\|off]` | Plausible-deniability mode |
261
262
  | `/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 |
262
263
  | `/panic [yes]` | Duress wipe — securely erase all on-disk secrets (session, history, trust, keys) and exit |
@@ -301,13 +302,14 @@ A green **✓** next to a name marks a SAS-verified peer; a red **✗** flags a
301
302
  | `/react <emoji>` | React to the last message — the emoji lands **on the message**, with a count when several people react |
302
303
  | `/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
304
  | `/pin` `/unpin` `/pins` | Pin messages |
304
- | `/sound` `/notify` | Sound / desktop notifications |
305
+ | `/sound` | Sound alerts on incoming messages |
306
+ | `/notify` | Desktop notifications — rate-limited, and muted for the session (with a line saying why) if the OS refuses them |
305
307
  | `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Do-not-disturb, mentions-only, or quiet hours |
306
308
  | `/clear` | Clear the chat |
307
309
 
308
310
  </details>
309
311
 
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.
312
+ Typing `:fire:` anywhere becomes 🔥 (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Shift+Enter** inserts a newline for multi-line messages — CipherMesh negotiates the kitty keyboard protocol / `modifyOtherKeys` on startup so the terminal can tell it apart from Enter, and **Alt+Enter** and **Ctrl+J** do the same on terminals that support neither (`CIPHERMESH_LEGACY_KEYS=1` turns the negotiation off). 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. Messages are laid out as blocks — a header naming the sender, then the text wrapped well short of the window and indented under it, with a coloured rule down the left of your own, of DMs and of anything that mentions you. Day separators and message grouping keep the log clean.
311
313
 
312
314
  ### First run & config file
313
315
 
package/README.pt-BR.md CHANGED
@@ -54,7 +54,7 @@ forwarding, imune a CGNAT).
54
54
  | 🗂️ | **Histórico local cifrado** | Opt-in (só com passphrase), Argon2id + XSalsa20-Poly1305, `/search` e `/export` |
55
55
  | 🖼️ | **Preview de imagens** | Fotos recebidas renderizam no chat em half-blocks coloridos |
56
56
  | 📎 | **Transferências com resume** | Chunks perdidos são re-pedidos; reconexão retoma de onde parou |
57
- | 💬 | **Cara de app moderno** | Suas mensagens à direita, avatar de emoji por usuário, reply com citação, `:fire:` → 🔥 |
57
+ | 💬 | **Cara de app moderno** | Mensagens em blocos com quebra de linha e uma barra colorida nas suas, avatar de emoji por usuário, reply com citação, `:fire:` → 🔥 |
58
58
  | 🎞️ | **Interface animada** | Splash na abertura, spinner de reconexão, barra de transferência viva (shimmer + ETA), cadeado fechando no handshake e um selo pulsante "novas mensagens ↓" |
59
59
  | 👻 | **Deniable e efêmeras** | Modo de negação plausível (crypto simétrica); mensagens efêmeras _queimam_ caractere a caractere ao expirar |
60
60
  | 🔒 | **Salas privadas** | `/create <sala> <senha>` — zero-knowledge: a senha nunca sai da sua máquina (Argon2id → challenge-response Ed25519) e o conteúdo da sala ganha uma camada simétrica extra que nem um relay malicioso atravessa |
@@ -226,7 +226,7 @@ aquela para a qual este software foi feito.
226
226
  | `/leave [sala]` | Sai de uma sala; o buffer fecha (a última sala é protegida) |
227
227
  | `/create <sala> <senha>` | Cria uma **sala privada** 🔒 — veja abaixo |
228
228
  | `/rooms` | Lista salas (🔒 marca as privadas) |
229
- | `/room` | Sala atual + sua lista de buffers |
229
+ | `/room` | Sala atual, como está enviando, e sua lista de buffers |
230
230
  | `/topic [texto\|clear]` | Mostra ou define o assunto da sala — aparece na barra de status e é sincronizado para quem entra depois |
231
231
  | `/owner` | Dono da sala |
232
232
  | `/kick` `/mute` `/ban` | Moderação (dono da sala) — presa à chave pública, então trocar de apelido não desfaz um ban |
@@ -252,10 +252,11 @@ sem verificar não leria uma palavra. Combine a senha por outro canal.
252
252
 
253
253
  | Comando | Descrição |
254
254
  | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
255
- | `/fingerprint [nick]` | Fingerprint + um **randomart** determinístico da chave |
256
- | `/verify <nick>` | Código SAS (~40 bits) + QR + randomart da chave para verificar |
255
+ | `/fingerprint [nick]` | Fingerprint, o fingerprint de identidade quando existe, e um **randomart** determinístico da chave |
256
+ | `/verify <nick>` | Código SAS (~40 bits) + QR + randomart da chave para verificar; diz se o código é sobre chaves de identidade ou de dispositivo |
257
257
  | `/verify-confirm <nick>` | Marca o peer como verificado |
258
258
  | `/backup [caminho]` | Backup cifrado da identidade + peers verificados (restaura no startup) |
259
+ | `/device [list\|request\|add\|accept\|remove]` | Seus dispositivos sob uma identidade. A chave de identidade nunca sai do dispositivo que a guarda: um segundo dispositivo recebe um lugar assinado na lista, não uma cópia da identidade. Remover um rotaciona a sala, então nada dito depois chega até ele |
259
260
  | `/trust <nick>` / `/trustlist` | Aceita chave nova / status de confiança |
260
261
  | `/contacts [add\|remove\|all]` | Agenda — apelidos persistentes nos registros de confiança ("esse fingerprint é o João"); aparece no `/users` e viaja no backup de identidade |
261
262
  | `/deniable [on\|off]` | Modo de negação plausível |
@@ -301,14 +302,15 @@ Um **✓** verde ao lado de um nome indica um peer verificado por SAS; um **✗*
301
302
  | `/react <emoji>` | Reage à última mensagem — o emoji aparece **na própria mensagem**, com contagem quando várias pessoas reagem |
302
303
  | `/edit` `/delete` | Edita ou apaga sua última mensagem — a **linha original é reescrita no lugar** (marcada _(edited)_) ou vira uma lápide, em vez de uma linha nova que você precisa juntar mentalmente à original |
303
304
  | `/pin` `/unpin` `/pins` | Fixa mensagens |
304
- | `/sound` `/notify` | Notificações sonoras / desktop |
305
+ | `/sound` | Alertas sonoros ao receber mensagens |
306
+ | `/notify` | Notificações de desktop — com limite de frequência, e silenciadas na sessão (com uma linha a dizer porquê) se o SO as recusar |
305
307
  | `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Não perturbe, só menções, ou horário silencioso |
306
308
  | `/clear` | Limpa o chat |
307
309
 
308
310
  </details>
309
311
 
310
312
  Digitar `:fire:` em qualquer lugar vira 🔥 (Tab autocompleta shortcodes).
311
- **Ctrl+K** abre uma paleta de comandos fuzzy, **Ctrl+E** um seletor de emoji. PageUp/PageDown rolam o histórico. **Alt+Enter** (ou Shift+Enter onde o terminal suporta, além de Ctrl+J) insere uma nova linha para mensagens de várias linhas; Enter envia. Colar texto multi-linha (código incluso) preserva as quebras — cola, confere, Enter. Markdown funciona: \`código\`, **negrito**, _itálico_, links, além de blocos de código \`\`\` e | tabelas |. Imagens recebidas têm preview inline (half-blocks) e renderizam em alta resolução com `/img` no kitty/iTerm2. Separadores de dia e agrupamento de mensagens deixam o log limpo.
313
+ **Ctrl+K** abre uma paleta de comandos fuzzy, **Ctrl+E** um seletor de emoji. PageUp/PageDown rolam o histórico. **Shift+Enter** insere uma nova linha para mensagens de várias linhas — o CipherMesh negocia o kitty keyboard protocol / `modifyOtherKeys` no arranque para o terminal conseguir distingui-lo do Enter, e **Alt+Enter** e **Ctrl+J** fazem o mesmo em terminais que não suportam nenhum dos dois (`CIPHERMESH_LEGACY_KEYS=1` desliga a negociação). Enter envia. Colar texto multi-linha (código incluso) preserva as quebras — cola, confere, Enter. Markdown funciona: \`código\`, **negrito**, _itálico_, links, além de blocos de código \`\`\` e | tabelas |. Imagens recebidas têm preview inline (half-blocks) e renderizam em alta resolução com `/img` no kitty/iTerm2. As mensagens são desenhadas em blocos — um cabeçalho com o remetente e, por baixo, o texto quebrado bem antes da largura da janela, com uma barra colorida à esquerda nas suas, nas DMs e em tudo o que te mencione. Separadores de dia e agrupamento de mensagens deixam o log limpo.
312
314
 
313
315
  ### Primeira execução & arquivo de config
314
316