ciphermesh 2.0.0 โ 2.2.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/README.md +40 -7
- package/README.pt-BR.md +40 -7
- package/bin/ciphermesh.js +5 -0
- package/docs/ARCHITECTURE.md +44 -1
- package/docs/PLUGINS.md +69 -0
- package/examples/plugins/poll.js +28 -0
- package/examples/plugins/roll.js +24 -0
- package/package.json +4 -3
- package/src/client/ChatController.js +814 -92
- package/src/client/UI.js +262 -6
- package/src/client/index.js +50 -7
- package/src/crypto/RoomKey.js +162 -0
- package/src/crypto/TrustStore.js +47 -0
- package/src/p2p/P2PChatController.js +9 -1
- package/src/protocol/messages.js +76 -8
- package/src/protocol/validators.js +61 -1
- package/src/server/SessionManager.js +124 -16
- package/src/server/WebSocketServer.js +339 -41
- package/src/shared/AuditLog.js +3 -0
- package/src/shared/PluginManager.js +9 -6
- package/src/shared/config.js +27 -2
- package/src/shared/constants.js +8 -0
- package/src/shared/lastSession.js +55 -0
- package/src/shared/onboarding.js +111 -0
package/README.md
CHANGED
|
@@ -53,7 +53,9 @@ forwarding, survives CGNAT).
|
|
|
53
53
|
| ๐ฌ | **Modern chat feel** | Right-aligned own messages, per-user emoji avatars, replies with quotes, `:fire:` โ ๐ฅ |
|
|
54
54
|
| ๐๏ธ | **Animated UI** | Splash intro, reconnect spinner, live transfer bars (shimmer + ETA), a lock-closing handshake on connect, and a pulsing "new messages โ" pill |
|
|
55
55
|
| ๐ป | **Deniable & ephemeral** | Symmetric-crypto deniable mode; ephemeral messages *burn away* char-by-char when they expire |
|
|
56
|
+
| ๐ | **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 |
|
|
56
57
|
| ๐ฐ๏ธ | **Serverless P2P mode** | mDNS peer discovery on the LAN โ no relay at all |
|
|
58
|
+
| ๐งฉ | **Plugins** | Drop a JS file in `~/.ciphermesh/plugins` and get new slash-commands โ `/roll` and `/poll` examples included ([Plugin API](docs/PLUGINS.md)) |
|
|
57
59
|
|
|
58
60
|
## ๐ Quick start
|
|
59
61
|
|
|
@@ -131,12 +133,27 @@ QR code) to whoever you want to pull in.
|
|
|
131
133
|
|
|
132
134
|
| Command | Description |
|
|
133
135
|
|---------|-------------|
|
|
134
|
-
| `/join <room
|
|
135
|
-
| `/
|
|
136
|
-
| `/room
|
|
136
|
+
| `/join <room> [password]` | Open a room as a **new buffer** โ you stay in your other rooms (IRC style) |
|
|
137
|
+
| `/leave [room]` | Leave a room; its buffer closes (the last room is protected) |
|
|
138
|
+
| `/create <room> <password>` | Create a **private room** ๐ โ see below |
|
|
139
|
+
| `/rooms` | List rooms (๐ marks private ones) |
|
|
140
|
+
| `/room` | Current room + your buffer list |
|
|
137
141
|
| `/owner` | Room owner |
|
|
138
142
|
| `/kick` `/mute` `/ban` | Owner moderation |
|
|
139
143
|
|
|
144
|
+
**Buffers:** be in several rooms at once โ **Alt+1..9** switches, and the status
|
|
145
|
+
bar shows `[1:general] [2:dev โข3]` with per-room unread badges. Because the
|
|
146
|
+
relay is blind (sealed sender), which room a message belongs to travels
|
|
147
|
+
*inside* the encrypted payload โ the server never learns it.
|
|
148
|
+
|
|
149
|
+
**Private rooms** are zero-knowledge: the password never leaves your machine.
|
|
150
|
+
Joining derives an Ed25519 key from the password (Argon2id) and answers a
|
|
151
|
+
server challenge with a signature โ the server stores only a verifier, in
|
|
152
|
+
memory, and it dies when the last member leaves (rooms are always ephemeral).
|
|
153
|
+
On top of that, everything said in a private room carries an extra symmetric
|
|
154
|
+
layer keyed by the password, so even a malicious relay that let someone in
|
|
155
|
+
without verifying couldn't read a word. Share the password out-of-band.
|
|
156
|
+
|
|
140
157
|
</details>
|
|
141
158
|
|
|
142
159
|
<details>
|
|
@@ -148,8 +165,10 @@ QR code) to whoever you want to pull in.
|
|
|
148
165
|
| `/verify <nick>` | SAS code (~40-bit) + QR + key randomart for out-of-band verification |
|
|
149
166
|
| `/verify-confirm <nick>` | Mark peer as verified |
|
|
150
167
|
| `/trust <nick>` / `/trustlist` | Accept new key / trust status |
|
|
168
|
+
| `/contacts [add\|remove\|all]` | Contact book โ persistent aliases on trust records ("this fingerprint is Joรฃo"); shows in `/users`, rides along in identity backups |
|
|
151
169
|
| `/backup [path]` | Encrypted backup of identity + verified peers (restore at startup) |
|
|
152
170
|
| `/deniable [on\|off]` | Plausible-deniability mode |
|
|
171
|
+
| `/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 |
|
|
153
172
|
| `/panic [yes]` | Duress wipe โ securely erase all on-disk secrets (session, history, trust, keys) and exit |
|
|
154
173
|
| `/cover [on\|constant\|off]` | Cover traffic โ `on` = jittered decoys, `constant` = steady-rate paced channel |
|
|
155
174
|
| `/theme [name]` | Nick colour theme: neon, matrix, mono, sunset, ocean |
|
|
@@ -183,7 +202,8 @@ A green **โ** next to a name marks a SAS-verified peer; a red **โ** flags a
|
|
|
183
202
|
|
|
184
203
|
| Command | Description |
|
|
185
204
|
|---------|-------------|
|
|
186
|
-
| `/away [reason]` / `/back` | Mark yourself away |
|
|
205
|
+
| `/away [reason]` / `/back` | Mark yourself away โ while away, unreads are counted (`[away ยท N new]`) and `/back` shows a summary |
|
|
206
|
+
| `/mentions [n]` | Recent mentions of you this session (who, where, when) |
|
|
187
207
|
| `/status <text\|off>` | Free-form status โ emojis welcome (`/status :fire: coding`) |
|
|
188
208
|
| `/react <emoji>` | React to the last message |
|
|
189
209
|
| `/edit` `/delete` | Edit/delete your last message |
|
|
@@ -194,11 +214,23 @@ A green **โ** next to a name marks a SAS-verified peer; a red **โ** flags a
|
|
|
194
214
|
|
|
195
215
|
</details>
|
|
196
216
|
|
|
197
|
-
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. 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.
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
### First run & config file
|
|
220
|
+
|
|
221
|
+
On your very first run, a **30-second setup wizard** walks you through nickname,
|
|
222
|
+
colour theme and default server (with a 3-line crash course on how the
|
|
223
|
+
encryption works), then saves everything so later runs go straight to the chat.
|
|
224
|
+
Re-run it anytime with `ciphermesh --setup`; skip it in scripts/CI with
|
|
225
|
+
`--no-onboard`.
|
|
198
226
|
|
|
199
|
-
|
|
227
|
+
CipherMesh also remembers your **last session**: the server prompt defaults to
|
|
228
|
+
where you were, and after connecting you land back in your last room
|
|
229
|
+
automatically (private rooms excluded โ their names never touch disk). Start
|
|
230
|
+
clean with `ciphermesh --fresh`.
|
|
200
231
|
|
|
201
|
-
|
|
232
|
+
The wizard writes `~/.ciphermesh/config.json` โ you can also edit it by hand.
|
|
233
|
+
All keys are optional (unknown keys are ignored):
|
|
202
234
|
|
|
203
235
|
```json
|
|
204
236
|
{
|
|
@@ -211,6 +243,7 @@ Drop a `~/.ciphermesh/config.json` to set defaults and skip the prompts. All key
|
|
|
211
243
|
"cover": "constant",
|
|
212
244
|
"theme": "matrix",
|
|
213
245
|
"autoAway": 10,
|
|
246
|
+
"autoLock": 5,
|
|
214
247
|
"dnd": "22:00-08:00"
|
|
215
248
|
}
|
|
216
249
|
```
|
package/README.pt-BR.md
CHANGED
|
@@ -53,7 +53,9 @@ forwarding, imune a CGNAT).
|
|
|
53
53
|
| ๐ฌ | **Cara de app moderno** | Suas mensagens ร direita, avatar de emoji por usuรกrio, reply com citaรงรฃo, `:fire:` โ ๐ฅ |
|
|
54
54
|
| ๐๏ธ | **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 โ" |
|
|
55
55
|
| ๐ป | **Deniable e efรชmeras** | Modo de negaรงรฃo plausรญvel (crypto simรฉtrica); mensagens efรชmeras *queimam* caractere a caractere ao expirar |
|
|
56
|
+
| ๐ | **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 |
|
|
56
57
|
| ๐ฐ๏ธ | **Modo P2P sem servidor** | Descoberta de peers via mDNS na LAN โ sem relay nenhum |
|
|
58
|
+
| ๐งฉ | **Plugins** | Solta um arquivo JS em `~/.ciphermesh/plugins` e ganha comandos novos โ exemplos `/roll` e `/poll` inclusos ([API de plugins](docs/PLUGINS.md)) |
|
|
57
59
|
|
|
58
60
|
## ๐ Comeรงando
|
|
59
61
|
|
|
@@ -131,12 +133,27 @@ quem vocรช quiser puxar pra conversa.
|
|
|
131
133
|
|
|
132
134
|
| Comando | Descriรงรฃo |
|
|
133
135
|
|---------|-----------|
|
|
134
|
-
| `/join <sala
|
|
135
|
-
| `/
|
|
136
|
-
| `/
|
|
136
|
+
| `/join <sala> [senha]` | Abre a sala como um **novo buffer** โ vocรช continua nas outras (estilo IRC) |
|
|
137
|
+
| `/leave [sala]` | Sai de uma sala; o buffer fecha (a รบltima sala รฉ protegida) |
|
|
138
|
+
| `/create <sala> <senha>` | Cria uma **sala privada** ๐ โ veja abaixo |
|
|
139
|
+
| `/rooms` | Lista salas (๐ marca as privadas) |
|
|
140
|
+
| `/room` | Sala atual + sua lista de buffers |
|
|
137
141
|
| `/owner` | Dono da sala |
|
|
138
142
|
| `/kick` `/mute` `/ban` | Moderaรงรฃo (dono da sala) |
|
|
139
143
|
|
|
144
|
+
**Buffers:** esteja em vรกrias salas ao mesmo tempo โ **Alt+1..9** alterna, e a
|
|
145
|
+
barra de status mostra `[1:general] [2:dev โข3]` com nรฃo-lidas por sala. Como o
|
|
146
|
+
relay รฉ cego (sealed sender), a qual sala cada mensagem pertence viaja *dentro*
|
|
147
|
+
do payload cifrado โ o servidor nunca fica sabendo.
|
|
148
|
+
|
|
149
|
+
**Salas privadas** sรฃo zero-knowledge: a senha nunca sai da sua mรกquina. Ao
|
|
150
|
+
entrar, o cliente deriva uma chave Ed25519 da senha (Argon2id) e responde um
|
|
151
|
+
desafio do servidor com uma assinatura โ o servidor guarda sรณ um verificador,
|
|
152
|
+
em memรณria, que morre quando a รบltima pessoa sai (salas sรฃo sempre efรชmeras).
|
|
153
|
+
Alรฉm disso, tudo que รฉ dito numa sala privada carrega uma camada simรฉtrica
|
|
154
|
+
extra derivada da senha: mesmo um relay malicioso que deixasse alguรฉm entrar
|
|
155
|
+
sem verificar nรฃo leria uma palavra. Combine a senha por outro canal.
|
|
156
|
+
|
|
140
157
|
</details>
|
|
141
158
|
|
|
142
159
|
<details>
|
|
@@ -149,7 +166,9 @@ quem vocรช quiser puxar pra conversa.
|
|
|
149
166
|
| `/verify-confirm <nick>` | Marca o peer como verificado |
|
|
150
167
|
| `/backup [caminho]` | Backup cifrado da identidade + peers verificados (restaura no startup) |
|
|
151
168
|
| `/trust <nick>` / `/trustlist` | Aceita chave nova / status de confianรงa |
|
|
169
|
+
| `/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 |
|
|
152
170
|
| `/deniable [on\|off]` | Modo de negaรงรฃo plausรญvel |
|
|
171
|
+
| `/lock` / `/autolock <min\|off>` | Trava a tela atrรกs da passphrase da sessรฃo โ na mรฃo ou apรณs inatividade (`autoLock` no config). Privacidade para o "saรญ um minuto"; o `/panic` รฉ para o pior minuto |
|
|
153
172
|
| `/panic [sim]` | Wipe de coaรงรฃo โ apaga com seguranรงa todos os segredos do disco (sessรฃo, histรณrico, confianรงa, chaves) e sai |
|
|
154
173
|
| `/cover [on\|constant\|off]` | Cover traffic โ `on` = iscas com jitter, `constant` = canal de taxa constante |
|
|
155
174
|
| `/theme [nome]` | Tema de cores dos nicks: neon, matrix, mono, sunset, ocean |
|
|
@@ -183,7 +202,8 @@ Um **โ** verde ao lado de um nome indica um peer verificado por SAS; um **โ*
|
|
|
183
202
|
|
|
184
203
|
| Comando | Descriรงรฃo |
|
|
185
204
|
|---------|-----------|
|
|
186
|
-
| `/away [motivo]` / `/back` | Marca/remove ausรชncia |
|
|
205
|
+
| `/away [motivo]` / `/back` | Marca/remove ausรชncia โ enquanto ausente, nรฃo-lidas sรฃo contadas (`[away ยท N new]`) e o `/back` mostra um resumo |
|
|
206
|
+
| `/mentions [n]` | Menรงรตes recentes a vocรช na sessรฃo (quem, onde, quando) |
|
|
187
207
|
| `/status <texto\|off>` | Status livre โ emoji ร vontade (`/status :fire: codando`) |
|
|
188
208
|
| `/react <emoji>` | Reage ร รบltima mensagem |
|
|
189
209
|
| `/edit` `/delete` | Edita/apaga sua รบltima mensagem |
|
|
@@ -195,11 +215,23 @@ Um **โ** verde ao lado de um nome indica um peer verificado por SAS; um **โ*
|
|
|
195
215
|
</details>
|
|
196
216
|
|
|
197
217
|
Digitar `:fire:` em qualquer lugar vira ๐ฅ (Tab autocompleta shortcodes).
|
|
198
|
-
**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. 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.
|
|
218
|
+
**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.
|
|
219
|
+
|
|
220
|
+
### Primeira execuรงรฃo & arquivo de config
|
|
221
|
+
|
|
222
|
+
Na primeirรญssima execuรงรฃo, um **wizard de 30 segundos** te guia por nickname,
|
|
223
|
+
tema de cores e servidor padrรฃo (com um mini-curso de 3 linhas de como a
|
|
224
|
+
criptografia funciona) e salva tudo โ das prรณximas vezes vocรช cai direto no
|
|
225
|
+
chat. Refaรงa quando quiser com `ciphermesh --setup`; pule em scripts/CI com
|
|
226
|
+
`--no-onboard`.
|
|
199
227
|
|
|
200
|
-
|
|
228
|
+
O CipherMesh tambรฉm lembra a sua **รบltima sessรฃo**: o prompt de servidor passa
|
|
229
|
+
a ter como padrรฃo onde vocรช estava, e depois de conectar vocรช volta sozinho
|
|
230
|
+
para a รบltima sala (salas privadas ficam de fora โ o nome delas nunca toca o
|
|
231
|
+
disco). Para comeรงar do zero: `ciphermesh --fresh`.
|
|
201
232
|
|
|
202
|
-
|
|
233
|
+
O wizard grava o `~/.ciphermesh/config.json` โ que vocรช tambรฉm pode editar na
|
|
234
|
+
mรฃo. Todas as chaves sรฃo opcionais (chaves desconhecidas sรฃo ignoradas):
|
|
203
235
|
|
|
204
236
|
```json
|
|
205
237
|
{
|
|
@@ -212,6 +244,7 @@ Crie um `~/.ciphermesh/config.json` para definir padrรตes e pular os prompts. To
|
|
|
212
244
|
"cover": "constant",
|
|
213
245
|
"theme": "matrix",
|
|
214
246
|
"autoAway": 10,
|
|
247
|
+
"autoLock": 5,
|
|
215
248
|
"dnd": "22:00-08:00"
|
|
216
249
|
}
|
|
217
250
|
```
|
package/bin/ciphermesh.js
CHANGED
|
@@ -19,6 +19,11 @@ Usage:
|
|
|
19
19
|
Options:
|
|
20
20
|
-h, --help show this help
|
|
21
21
|
-v, --version show the version
|
|
22
|
+
|
|
23
|
+
Client options:
|
|
24
|
+
--setup re-run the first-run setup wizard
|
|
25
|
+
--no-onboard skip the first-run wizard (scripts/CI)
|
|
26
|
+
--fresh forget the last session (server/room) and start clean
|
|
22
27
|
`;
|
|
23
28
|
|
|
24
29
|
const arg = process.argv[2];
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -448,7 +448,15 @@ Types:
|
|
|
448
448
|
| `peer_left` | Server -> Clients | A peer left |
|
|
449
449
|
| `key_exchange` | Client -> Server -> Client | Public key exchange between peers |
|
|
450
450
|
| `encrypted_message` | Client -> Server -> Client | Encrypted message |
|
|
451
|
-
| `
|
|
451
|
+
| `change_room` | Client -> Server | Legacy single-room switch: leave every room, enter one (+ optional `roomAuthPk` when creating a private room) |
|
|
452
|
+
| `room_changed` | Server -> Client | Room switch confirmed (+ `private` flag) |
|
|
453
|
+
| `join_room` | Client -> Server | Multi-room: join an ADDITIONAL room, keeping current ones (same `roomAuthPk` option) |
|
|
454
|
+
| `room_joined` | Server -> Client | Additive join confirmed (`room`, `peers`, `private`, `roomOwner`) |
|
|
455
|
+
| `leave_room` / `room_left` | Client <-> Server | Leave one room (the last one is refused โ a session is always somewhere) |
|
|
456
|
+
| `list_rooms` / `room_list` | Client <-> Server | Room list (each room carries a `private` flag) |
|
|
457
|
+
| `room_challenge` | Server -> Client | Target room is private โ sign this nonce (see 6.9) |
|
|
458
|
+
| `room_auth` | Client -> Server | Ed25519 signature proving password knowledge (see 6.9) |
|
|
459
|
+
| `error` | Server -> Client | Error (duplicate nickname, wrong room password, etc.) |
|
|
452
460
|
| `ping` / `pong` | Bidirectional | Heartbeat |
|
|
453
461
|
|
|
454
462
|
#### `src/protocol/validators.js` โ Validation
|
|
@@ -733,6 +741,41 @@ fingerprint = SHA-256(publicKey)
|
|
|
733
741
|
= "A1B2:C3D4:E5F6:7890"
|
|
734
742
|
```
|
|
735
743
|
|
|
744
|
+
### 6.9 Private Rooms (password-protected, zero-knowledge)
|
|
745
|
+
|
|
746
|
+
`/create <room> <password>` creates a room the server can gate **without ever
|
|
747
|
+
seeing the password** (`src/crypto/RoomKey.js`):
|
|
748
|
+
|
|
749
|
+
```
|
|
750
|
+
seed(64B) = Argon2id(password, salt = BLAKE2b("ciphermesh/room-v1:" + room))
|
|
751
|
+
โโโ authSeed (32B) โ Ed25519 keypair (join authentication)
|
|
752
|
+
โโโ roomKey (32B) โ secretbox key (room content layer)
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Join authentication (challenge-response):**
|
|
756
|
+
```
|
|
757
|
+
1. Creator sends change_room + roomAuthPk (the Ed25519 PUBLIC key).
|
|
758
|
+
The server stores it as the room's verifier โ in memory only.
|
|
759
|
+
2. A joiner sends change_room โ server replies room_challenge { nonce }.
|
|
760
|
+
3. The joiner signs "ciphermesh/room-auth-v1:room:nonce:sessionId" with the
|
|
761
|
+
password-derived secret key โ room_auth { signature }.
|
|
762
|
+
4. The server verifies against the stored verifier. Wrong password โ a
|
|
763
|
+
different keypair โ invalid signature โ ROOM_AUTH_FAILED.
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
The signature binds room, nonce **and** sessionId, so it can't be replayed by
|
|
767
|
+
another connection. Wrong-password attempts are throttled per connection, and
|
|
768
|
+
each guess costs the attacker a full Argon2id derivation client-side. When the
|
|
769
|
+
last member leaves, the room dies and the verifier with it โ same ephemeral
|
|
770
|
+
lifecycle as public rooms.
|
|
771
|
+
|
|
772
|
+
**Content layer:** every payload sent in a private room is wrapped as
|
|
773
|
+
`{ rk: 1, n, c }` โ `crypto_secretbox(payload, roomKey)` โ *before* the normal
|
|
774
|
+
pairwise encryption (ratchet/sealed sender). So even a malicious relay that
|
|
775
|
+
skipped verification and injected a member could not read the room: reading
|
|
776
|
+
requires the password. The outer layer already pads ciphertexts, so the
|
|
777
|
+
wrapper adds no metadata leak.
|
|
778
|
+
|
|
736
779
|
---
|
|
737
780
|
|
|
738
781
|
## 7. Handshake Protocol
|
package/docs/PLUGINS.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# CipherMesh Plugin API
|
|
2
|
+
|
|
3
|
+
CipherMesh loads user plugins at startup from `~/.ciphermesh/plugins/*.js` and
|
|
4
|
+
routes unknown slash-commands to them. Plugins work in both relay and P2P mode.
|
|
5
|
+
|
|
6
|
+
## Quick start
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
mkdir -p ~/.ciphermesh/plugins
|
|
10
|
+
cp examples/plugins/roll.js examples/plugins/poll.js ~/.ciphermesh/plugins/
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Restart the client โ `/plugins` lists what loaded, and `/roll 2d20+3` /
|
|
14
|
+
`/poll Pizza tonight? | yes | obviously` just work.
|
|
15
|
+
|
|
16
|
+
## Plugin format
|
|
17
|
+
|
|
18
|
+
A plugin is an ES module whose **default export** is:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
export default {
|
|
22
|
+
name: 'roll', // required, unique
|
|
23
|
+
description: 'Roll dice', // optional, shown by /plugins
|
|
24
|
+
commands: {
|
|
25
|
+
// key = command name (with or without the leading slash)
|
|
26
|
+
roll(args) {
|
|
27
|
+
// args: string[] โ everything typed after the command, split on spaces
|
|
28
|
+
return { send: '๐ฒ 4' };
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Files that fail to import, or lack `name`/`commands`, are skipped silently.
|
|
35
|
+
|
|
36
|
+
## Handler return values
|
|
37
|
+
|
|
38
|
+
| Return | Effect |
|
|
39
|
+
|--------|--------|
|
|
40
|
+
| `{ send: '<text>' }` | The text is **sent to the current room** as a normal end-to-end-encrypted message (and echoed locally). Markdown and multi-line text work. |
|
|
41
|
+
| `{ info: '<text>' }` | Shown **only locally** as an info line. |
|
|
42
|
+
| `'<text>'` (plain string) | Same as `{ info }` โ the original API, still supported. |
|
|
43
|
+
| `null` / `undefined` / throw | Treated as "not handled": the user sees *Unknown command*. |
|
|
44
|
+
|
|
45
|
+
Handlers are synchronous โ return the final value directly.
|
|
46
|
+
|
|
47
|
+
## Precedence and naming
|
|
48
|
+
|
|
49
|
+
Built-in commands always win: a plugin command named `/help` or `/join` is
|
|
50
|
+
never reached. Plugin commands are only tried when no built-in matches. If two
|
|
51
|
+
plugins register the same command, the one loaded last wins (load order is the
|
|
52
|
+
directory listing).
|
|
53
|
+
|
|
54
|
+
## Security model โ read this
|
|
55
|
+
|
|
56
|
+
A plugin is **arbitrary JavaScript running inside your chat process**, with
|
|
57
|
+
your privileges and full access to your keys in memory. There is no sandbox.
|
|
58
|
+
|
|
59
|
+
- Only install plugins you wrote or read line-by-line.
|
|
60
|
+
- Treat a plugin file like you treat `curl | sh`.
|
|
61
|
+
- Plugins are never synced, auto-updated or downloaded by CipherMesh โ the
|
|
62
|
+
only way code gets into `~/.ciphermesh/plugins/` is you putting it there.
|
|
63
|
+
|
|
64
|
+
## Included examples
|
|
65
|
+
|
|
66
|
+
- [`examples/plugins/roll.js`](../examples/plugins/roll.js) โ dice roller,
|
|
67
|
+
D&D notation (`/roll 2d20+3`), result goes to the room.
|
|
68
|
+
- [`examples/plugins/poll.js`](../examples/plugins/poll.js) โ quick poll
|
|
69
|
+
(`/poll question | opt A | opt B`), voted with the built-in `/react`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// CipherMesh example plugin: quick poll voted with reactions.
|
|
2
|
+
// Install: cp poll.js ~/.ciphermesh/plugins/
|
|
3
|
+
const NUMBERS = ['1๏ธโฃ', '2๏ธโฃ', '3๏ธโฃ', '4๏ธโฃ', '5๏ธโฃ', '6๏ธโฃ', '7๏ธโฃ', '8๏ธโฃ', '9๏ธโฃ'];
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'poll',
|
|
7
|
+
description: 'Create a poll: /poll <question> | <option> | <option> [| โฆ]',
|
|
8
|
+
commands: {
|
|
9
|
+
poll(args) {
|
|
10
|
+
const raw = args.join(' ');
|
|
11
|
+
if (!raw.includes('|')) {
|
|
12
|
+
return { info: 'Usage: /poll <question> | <option> | <option> [| โฆ]' };
|
|
13
|
+
}
|
|
14
|
+
const [question, ...options] = raw
|
|
15
|
+
.split('|')
|
|
16
|
+
.map((s) => s.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
if (!question || options.length < 2 || options.length > NUMBERS.length) {
|
|
19
|
+
return { info: `A poll needs a question and 2-${NUMBERS.length} options.` };
|
|
20
|
+
}
|
|
21
|
+
const lines = [`๐ **${question}**`];
|
|
22
|
+
options.forEach((opt, i) => lines.push(`${NUMBERS[i]} ${opt}`));
|
|
23
|
+
lines.push(`Vote by reacting โ e.g. /react ${NUMBERS[0]}`);
|
|
24
|
+
// `send` โ delivered to the whole room as a normal E2EE message.
|
|
25
|
+
return { send: lines.join('\n') };
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// CipherMesh example plugin: dice roller (D&D notation).
|
|
2
|
+
// Install: cp roll.js ~/.ciphermesh/plugins/
|
|
3
|
+
export default {
|
|
4
|
+
name: 'roll',
|
|
5
|
+
description: 'Roll dice: /roll [NdM+K] โ e.g. /roll, /roll d20, /roll 2d20+3',
|
|
6
|
+
commands: {
|
|
7
|
+
roll(args) {
|
|
8
|
+
const spec = (args[0] || 'd6').toLowerCase();
|
|
9
|
+
const m = spec.match(/^(\d{0,2})d(\d{1,4})([+-]\d{1,4})?$/);
|
|
10
|
+
if (!m) {
|
|
11
|
+
return { info: 'Usage: /roll [NdM(+K)] โ e.g. /roll 2d20+3' };
|
|
12
|
+
}
|
|
13
|
+
const count = Math.min(parseInt(m[1] || '1', 10) || 1, 20);
|
|
14
|
+
const sides = Math.max(2, parseInt(m[2], 10));
|
|
15
|
+
const modifier = parseInt(m[3] || '0', 10);
|
|
16
|
+
const rolls = Array.from({ length: count }, () => 1 + Math.floor(Math.random() * sides));
|
|
17
|
+
const total = rolls.reduce((a, b) => a + b, 0) + modifier;
|
|
18
|
+
const detail =
|
|
19
|
+
count > 1 || modifier ? ` (${rolls.join(' + ')}${modifier ? ` ${m[3]}` : ''})` : '';
|
|
20
|
+
// `send` โ delivered to the whole room as a normal E2EE message.
|
|
21
|
+
return { send: `๐ฒ rolled ${spec}: **${total}**${detail}` };
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ciphermesh",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Secure terminal chat for the local network (LAN) with real end-to-end encryption (E2EE) using libsodium",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/client/index.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"bin",
|
|
12
12
|
"src",
|
|
13
13
|
"docs",
|
|
14
|
+
"examples",
|
|
14
15
|
"README.md",
|
|
15
16
|
"README.pt-BR.md",
|
|
16
17
|
"LICENSE"
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
"bonjour-service": "1.4.3",
|
|
55
56
|
"boxen": "8.0.1",
|
|
56
57
|
"chalk": "^5.4.1",
|
|
57
|
-
"figlet": "1.11.
|
|
58
|
+
"figlet": "1.11.3",
|
|
58
59
|
"gradient-string": "3.0.0",
|
|
59
60
|
"jimp": "1.6.1",
|
|
60
61
|
"node-notifier": "10.0.1",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
},
|
|
65
66
|
"devDependencies": {
|
|
66
67
|
"@eslint/js": "10.0.1",
|
|
67
|
-
"eslint": "10.
|
|
68
|
+
"eslint": "10.8.0",
|
|
68
69
|
"globals": "^17.7.0",
|
|
69
70
|
"prettier": "^3.9.5"
|
|
70
71
|
},
|