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/README.pt-BR.md CHANGED
@@ -41,28 +41,28 @@ forwarding, imune a CGNAT).
41
41
 
42
42
  ## ✨ Destaques
43
43
 
44
- | | Feature | Resumo |
45
- |-----|---------|--------|
46
- | 🔐 | **E2EE de verdade** | Curve25519 + XSalsa20-Poly1305 via libsodium, chaves em `sodium_malloc` — nunca tocam o disco |
47
- | 🔄 | **Perfect Forward Secrecy** | Double Ratchet: uma chave por mensagem — comprometer hoje ≠ ler ontem |
48
- | 🛡️ | **Pós-quântico híbrido** | X25519 **+ ML-KEM-768** misturado na raiz do ratchet — vence o "grava hoje, decifra depois" mantendo segurança ≥ à clássica ([detalhes](docs/ARCHITECTURE.md)) |
49
- | 🕶️ | **Resistência a metadados** | **Sealed sender** — o relay nunca vê quem enviou a mensagem — + padding de comprimento em buckets fixos em todo ciphertext e cover traffic opcional (`/cover`) |
50
- | 🕵️ | **TOFU + SAS** | Alarme de troca de chave (MITM), código de 6 dígitos verificável por voz e badges de confiança **✓/✗** inline ao lado dos nomes |
51
- | 🌐 | **LAN e internet** | Detecta Tailscale sozinho e mostra o endereço alcançável no banner |
52
- | 📨 | **Convites com QR** | `/invite` gera uma string `ciphermesh://` + QR — colou, caiu na sala certa |
53
- | ✓✓ | **Read receipts cifrados** | O ✓✓ viaja como ciphertext comum — o servidor não distingue de mensagem |
54
- | 🗂️ | **Histórico local cifrado** | Opt-in (só com passphrase), Argon2id + XSalsa20-Poly1305, `/search` e `/export` |
55
- | 🖼️ | **Preview de imagens** | Fotos recebidas renderizam no chat em half-blocks coloridos |
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:` → 🔥 |
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
- | 👻 | **Deniable e efêmeras** | Modo de negação plausível (crypto simétrica); mensagens efêmeras *queimam* caractere a caractere ao expirar |
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 |
61
- | 🗂️ | **Buffers multi-sala** | Fique em várias salas ao mesmo tempo — **Alt+1..9** alterna, com não-lidas por sala. A qual sala cada mensagem pertence viaja *dentro* do payload cifrado: o relay nunca fica sabendo |
62
- | 🩺 | **Ele se explica sozinho** | `/doctor` diagnostica uma conexão que falha camada por camada — endereço, DNS, TCP, TLS, protocolo — e diz o que fazer em cada falha |
63
- | 🔐 | **Trava de tela** | `/lock` e `/autolock` põem a sessão atrás da sua passphrase quando você sai da frente; o `/panic` continua ali para o pior momento |
64
- | 🛰️ | **Modo P2P sem servidor** | Descoberta de peers via mDNS na LAN — sem relay nenhum, e com quase o mesmo conjunto de comandos |
65
- | 🧩 | **Plugins** | Solta um arquivo JS em `~/.ciphermesh/plugins` e ganha comandos novos — exemplos `/roll` e `/poll` inclusos ([API de plugins](docs/PLUGINS.md)) |
44
+ | | Feature | Resumo |
45
+ | --- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | 🔐 | **E2EE de verdade** | Curve25519 + XSalsa20-Poly1305 via libsodium, chaves em `sodium_malloc` — nunca tocam o disco |
47
+ | 🔄 | **Perfect Forward Secrecy** | Double Ratchet: uma chave por mensagem — comprometer hoje ≠ ler ontem |
48
+ | 🛡️ | **Pós-quântico híbrido** | X25519 **+ ML-KEM-768** misturado na raiz do ratchet — vence o "grava hoje, decifra depois" mantendo segurança ≥ à clássica ([detalhes](docs/ARCHITECTURE.md)) |
49
+ | 🕶️ | **Resistência a metadados** | **Sealed sender** — o relay nunca vê quem enviou a mensagem — + padding de comprimento em buckets fixos em todo ciphertext e cover traffic opcional (`/cover`) |
50
+ | 🕵️ | **TOFU + SAS** | Alarme de troca de chave (MITM), código de 6 dígitos verificável por voz e badges de confiança **✓/✗** inline ao lado dos nomes |
51
+ | 🌐 | **LAN e internet** | Detecta Tailscale sozinho e mostra o endereço alcançável no banner |
52
+ | 📨 | **Convites com QR** | `/invite` gera uma string `ciphermesh://` + QR — colou, caiu na sala certa |
53
+ | ✓✓ | **Read receipts cifrados** | O ✓✓ viaja como ciphertext comum — o servidor não distingue de mensagem |
54
+ | 🗂️ | **Histórico local cifrado** | Opt-in (só com passphrase), Argon2id + XSalsa20-Poly1305, `/search` e `/export` |
55
+ | 🖼️ | **Preview de imagens** | Fotos recebidas renderizam no chat em half-blocks coloridos |
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:` → 🔥 |
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
+ | 👻 | **Deniable e efêmeras** | Modo de negação plausível (crypto simétrica); mensagens efêmeras _queimam_ caractere a caractere ao expirar |
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 |
61
+ | 🗂️ | **Buffers multi-sala** | Fique em várias salas ao mesmo tempo — **Alt+1..9** alterna, com não-lidas por sala. A qual sala cada mensagem pertence viaja _dentro_ do payload cifrado: o relay nunca fica sabendo |
62
+ | 🩺 | **Ele se explica sozinho** | `/doctor` diagnostica uma conexão que falha camada por camada — endereço, DNS, TCP, TLS, protocolo — e diz o que fazer em cada falha |
63
+ | 🔐 | **Trava de tela** | `/lock` e `/autolock` põem a sessão atrás da sua passphrase quando você sai da frente; o `/panic` continua ali para o pior momento |
64
+ | 🛰️ | **Modo P2P sem servidor** | Descoberta de peers via mDNS na LAN — sem relay nenhum, e com quase o mesmo conjunto de comandos |
65
+ | 🧩 | **Plugins** | Solta um arquivo JS em `~/.ciphermesh/plugins` e ganha comandos novos — exemplos `/roll` e `/poll` inclusos ([API de plugins](docs/PLUGINS.md)) |
66
66
 
67
67
  ## 🚀 Começando
68
68
 
@@ -132,10 +132,10 @@ do
132
132
  > nunca embutiram o addon nativo e só rodavam na máquina que os construiu, então
133
133
  > aqueles anexos foram removidos.
134
134
 
135
- | Binário | O que é |
136
- |---|---|
137
- | `ciphermesh-<plataforma>` | Tudo: cliente, relay e P2P. `ciphermesh server` e `ciphermesh p2p` funcionam igual ao npm. |
138
- | `ciphermesh-server-<plataforma>` | Só o relay, para quem hospeda e não quer mais nada na máquina. |
135
+ | Binário | O que é |
136
+ | -------------------------------- | ------------------------------------------------------------------------------------------ |
137
+ | `ciphermesh-<plataforma>` | Tudo: cliente, relay e P2P. `ciphermesh server` e `ciphermesh p2p` funcionam igual ao npm. |
138
+ | `ciphermesh-server-<plataforma>` | Só o relay, para quem hospeda e não quer mais nada na máquina. |
139
139
 
140
140
  **Todo mundo** (incluindo quem hospeda):
141
141
 
@@ -202,38 +202,39 @@ aquela para a qual este software foi feito.
202
202
  <details>
203
203
  <summary><b>Essenciais</b></summary>
204
204
 
205
- | Comando | Descrição |
206
- |---------|-----------|
207
- | `/help` | Todos os comandos |
208
- | `/tips` | Mostra uma dica rotativa de segurança/UX |
209
- | `/users` | Quem está online (com away/status) |
210
- | `/msg <nick> <texto>` | Mensagem privada (DM) |
211
- | `/reply <texto>` | Responde citando a última mensagem recebida |
212
- | `/me <ação>` | Ação em terceira pessoa — *«felipe está compilando»* |
205
+ | Comando | Descrição |
206
+ | ----------------------------- | ----------------------------------------------------------------------- |
207
+ | `/help` | Todos os comandos |
208
+ | `/tips` | Mostra uma dica rotativa de segurança/UX |
209
+ | `/users` | Quem está online (com away/status) |
210
+ | `/msg <nick> <texto>` | Mensagem privada (DM) |
211
+ | `/reply <texto>` | Responde citando a última mensagem recebida |
212
+ | `/me <ação>` | Ação em terceira pessoa — _«ana está compilando»_ |
213
213
  | `/watch [add\|remove\|clear]` | Alerta quando uma palavra aparece em **qualquer** sala, como uma menção |
214
- | `/invite [host:porta]` | Gera convite `ciphermesh://` + QR code |
215
- | `/nick <novo>` | Troca de apelido (antes de entrar — recupera de "apelido em uso") |
216
- | `/quit` | Sair |
214
+ | `/invite [host:porta]` | Gera convite `ciphermesh://` + QR code |
215
+ | `/nick <novo>` | Troca de apelido (antes de entrar — recupera de "apelido em uso") |
216
+ | `/quit` | Sair |
217
217
 
218
218
  </details>
219
219
 
220
220
  <details>
221
221
  <summary><b>Salas</b></summary>
222
222
 
223
- | Comando | Descrição |
224
- |---------|-----------|
225
- | `/join <sala> [senha]` | Abre a sala como um **novo buffer** — você continua nas outras (estilo IRC) |
226
- | `/leave [sala]` | Sai de uma sala; o buffer fecha (a última sala é protegida) |
227
- | `/create <sala> <senha>` | Cria uma **sala privada** 🔒 — veja abaixo |
228
- | `/rooms` | Lista salas (🔒 marca as privadas) |
229
- | `/room` | Sala atual + sua lista de buffers |
230
- | `/topic [texto\|clear]` | Mostra ou define o assunto da sala — aparece na barra de status e é sincronizado para quem entra depois |
231
- | `/owner` | Dono da sala |
232
- | `/kick` `/mute` `/ban` | Moderação (dono da sala) |
223
+ | Comando | Descrição |
224
+ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
225
+ | `/join <sala> [senha]` | Abre a sala como um **novo buffer** — você continua nas outras (estilo IRC) |
226
+ | `/leave [sala]` | Sai de uma sala; o buffer fecha (a última sala é protegida) |
227
+ | `/create <sala> <senha>` | Cria uma **sala privada** 🔒 — veja abaixo |
228
+ | `/rooms` | Lista salas (🔒 marca as privadas) |
229
+ | `/room` | Sala atual + sua lista de buffers |
230
+ | `/topic [texto\|clear]` | Mostra ou define o assunto da sala — aparece na barra de status e é sincronizado para quem entra depois |
231
+ | `/owner` | Dono da sala |
232
+ | `/kick` `/mute` `/ban` | Moderação (dono da sala) — presa à chave pública, então trocar de apelido não desfaz um ban |
233
+ | `/block` `/unblock` `/blocklist` | Pare de ver alguém, **só para você**. Nada é enviado, o relay nunca fica sabendo e a pessoa não é avisada — por isso qualquer um pode usar, inclusive na `general`, que não tem dono. Funciona no P2P também, onde não há moderação nenhuma. |
233
234
 
234
235
  **Buffers:** esteja em várias salas ao mesmo tempo — **Alt+1..9** alterna, e a
235
236
  barra de status mostra `[1:general] [2:dev •3]` com não-lidas por sala. Como o
236
- relay é cego (sealed sender), a qual sala cada mensagem pertence viaja *dentro*
237
+ relay é cego (sealed sender), a qual sala cada mensagem pertence viaja _dentro_
237
238
  do payload cifrado — o servidor nunca fica sabendo.
238
239
 
239
240
  **Salas privadas** são zero-knowledge: a senha nunca sai da sua máquina. Ao
@@ -249,22 +250,22 @@ sem verificar não leria uma palavra. Combine a senha por outro canal.
249
250
  <details>
250
251
  <summary><b>Confiança & segurança</b></summary>
251
252
 
252
- | Comando | Descrição |
253
- |---------|-----------|
254
- | `/fingerprint [nick]` | Fingerprint + um **randomart** determinístico da chave |
255
- | `/verify <nick>` | Código SAS (~40 bits) + QR + randomart da chave para verificar |
256
- | `/verify-confirm <nick>` | Marca o peer como verificado |
257
- | `/backup [caminho]` | Backup cifrado da identidade + peers verificados (restaura no startup) |
258
- | `/trust <nick>` / `/trustlist` | Aceita chave nova / status de confiança |
259
- | `/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 |
260
- | `/deniable [on\|off]` | Modo de negação plausível |
253
+ | Comando | Descrição |
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 |
257
+ | `/verify-confirm <nick>` | Marca o peer como verificado |
258
+ | `/backup [caminho]` | Backup cifrado da identidade + peers verificados (restaura no startup) |
259
+ | `/trust <nick>` / `/trustlist` | Aceita chave nova / status de confiança |
260
+ | `/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
+ | `/deniable [on\|off]` | Modo de negação plausível |
261
262
  | `/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 |
262
- | `/panic [sim]` | Wipe de coação — apaga com segurança todos os segredos do disco (sessão, histórico, confiança, chaves) e sai |
263
- | `/cover [on\|constant\|off]` | Cover traffic — `on` = iscas com jitter, `constant` = canal de taxa constante |
264
- | `/theme [nome]` | Tema de cores dos nicks: neon, matrix, mono, sunset, ocean |
265
- | `/ephemeral <30s\|5m\|1h\|off>` | Mensagens autodestrutivas |
266
- | `/receipts [on\|off]` | Envio de confirmação de leitura (✓✓) |
267
- | `/audit [n]` | Log de auditoria local |
263
+ | `/panic [sim]` | Wipe de coação — apaga com segurança todos os segredos do disco (sessão, histórico, confiança, chaves) e sai |
264
+ | `/cover [on\|constant\|off]` | Cover traffic — `on` = iscas com jitter, `constant` = canal de taxa constante |
265
+ | `/theme [nome]` | Tema de cores dos nicks: neon, matrix, mono, sunset, ocean |
266
+ | `/ephemeral <30s\|5m\|1h\|off>` | Mensagens autodestrutivas |
267
+ | `/receipts [on\|off]` | Envio de confirmação de leitura (✓✓) |
268
+ | `/audit [n]` | Log de auditoria local |
268
269
 
269
270
  Um **✓** verde ao lado de um nome indica um peer verificado por SAS; um **✗** vermelho sinaliza uma chave que mudou desde a última vez (possível MITM). Um peer novo não-verificado dispara um lembrete único para `/verify`.
270
271
 
@@ -273,41 +274,41 @@ Um **✓** verde ao lado de um nome indica um peer verificado por SAS; um **✗*
273
274
  <details>
274
275
  <summary><b>Histórico & arquivos</b></summary>
275
276
 
276
- | Comando | Descrição |
277
- |---------|-----------|
278
- | `/file <caminho>` | Oferece arquivo (≤ 50MB) — o destinatário precisa dar `/accept`; retoma |
279
- | `/voice [seg]` | Grava e envia nota de voz cifrada (precisa de `sox`/`ffmpeg`; default 10s) |
280
- | `/play [caminho]` | Toca a última nota de voz recebida (`afplay`/`sox`/`ffplay`) |
281
- | `/accept [id]` / `/reject [id]` | Aceita / recusa uma oferta de arquivo recebida |
282
- | `/img [caminho]` | Renderiza a última imagem recebida em **alta resolução** (kitty/iTerm2) |
283
- | `/retention <7d\|24h\|30m>` | Purga o histórico local mais antigo que o tempo dado |
284
- | `/search <termo>` | Busca no histórico local cifrado (em disco, entre sessões) |
285
- | `/find [termo]` — **Ctrl+F** | Busca **no histórico da sala na tela** e, com Enter, **salta para a mensagem** destacada |
286
- | `/doctor [host:porta]` | Diagnostica por que a conexão falha: endereço, DNS, porta TCP, TLS (CA ou self-signed) e versão de protocolo — cada falha com o que fazer |
287
- | `/history [n]` | Últimas n mensagens do histórico |
288
- | `/export [caminho]` | Exporta o histórico em .txt ou .json (texto plano!) |
277
+ | Comando | Descrição |
278
+ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
279
+ | `/file <caminho>` | Oferece arquivo (≤ 50MB) — o destinatário precisa dar `/accept`; retoma |
280
+ | `/voice [seg]` | Grava e envia nota de voz cifrada (precisa de `sox`/`ffmpeg`; default 10s) |
281
+ | `/play [caminho]` | Toca a última nota de voz recebida (`afplay`/`sox`/`ffplay`) |
282
+ | `/accept [id]` / `/reject [id]` | Aceita / recusa uma oferta de arquivo recebida |
283
+ | `/img [caminho]` | Renderiza a última imagem recebida em **alta resolução** (kitty/iTerm2) |
284
+ | `/retention <7d\|24h\|30m>` | Purga o histórico local mais antigo que o tempo dado |
285
+ | `/search <termo>` | Busca no histórico local cifrado (em disco, entre sessões) |
286
+ | `/find [termo]` — **Ctrl+F** | Busca **no histórico da sala na tela** e, com Enter, **salta para a mensagem** destacada |
287
+ | `/doctor [host:porta]` | Diagnostica por que a conexão falha: endereço, DNS, porta TCP, TLS (CA ou self-signed) e versão de protocolo — cada falha com o que fazer |
288
+ | `/history [n]` | Últimas n mensagens do histórico |
289
+ | `/export [caminho]` | Exporta o histórico em .txt ou .json (texto plano!) |
289
290
 
290
291
  </details>
291
292
 
292
293
  <details>
293
294
  <summary><b>Presença & diversão</b></summary>
294
295
 
295
- | Comando | Descrição |
296
- |---------|-----------|
297
- | `/away [motivo]` / `/back` | Marca/remove ausência — enquanto ausente, não-lidas são contadas (`[away · N new]`) e o `/back` mostra um resumo |
298
- | `/mentions [n]` | Menções recentes a você na sessão (quem, onde, quando) |
299
- | `/status <texto\|off>` | Status livre — emoji à vontade (`/status :fire: codando`) |
300
- | `/react <emoji>` | Reage à última mensagem — o emoji aparece **na própria mensagem**, com contagem quando várias pessoas reagem |
301
- | `/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 |
302
- | `/pin` `/unpin` `/pins` | Fixa mensagens |
303
- | `/sound` `/notify` | Notificações sonoras / desktop |
304
- | `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Não perturbe, só menções, ou horário silencioso |
305
- | `/clear` | Limpa o chat |
296
+ | Comando | Descrição |
297
+ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
298
+ | `/away [motivo]` / `/back` | Marca/remove ausência — enquanto ausente, não-lidas são contadas (`[away · N new]`) e o `/back` mostra um resumo |
299
+ | `/mentions [n]` | Menções recentes a você na sessão (quem, onde, quando) |
300
+ | `/status <texto\|off>` | Status livre — emoji à vontade (`/status :fire: codando`) |
301
+ | `/react <emoji>` | Reage à última mensagem — o emoji aparece **na própria mensagem**, com contagem quando várias pessoas reagem |
302
+ | `/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
+ | `/pin` `/unpin` `/pins` | Fixa mensagens |
304
+ | `/sound` `/notify` | Notificações sonoras / desktop |
305
+ | `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Não perturbe, só menções, ou horário silencioso |
306
+ | `/clear` | Limpa o chat |
306
307
 
307
308
  </details>
308
309
 
309
310
  Digitar `:fire:` em qualquer lugar vira 🔥 (Tab autocompleta shortcodes).
310
- **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.
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.
311
312
 
312
313
  ### Primeira execução & arquivo de config
313
314
 
@@ -327,7 +328,7 @@ mão. Todas as chaves são opcionais (chaves desconhecidas são ignoradas):
327
328
 
328
329
  ```json
329
330
  {
330
- "nickname": "felipe",
331
+ "nickname": "ana",
331
332
  "server": "wss://100.x.y.z:3600",
332
333
  "sound": false,
333
334
  "notify": true,
@@ -364,7 +365,7 @@ mão. Todas as chaves são opcionais (chaves desconhecidas são ignoradas):
364
365
  **Argon2id + XSalsa20-Poly1305** — sem passphrase, nada persiste.
365
366
  - **Pós-quântico híbrido**: cada sessão mistura um segredo ML-KEM-768 na raiz
366
367
  do ratchet na inicialização, então tráfego gravado hoje continua ilegível
367
- para um adversário quântico futuro. Ele é *somado* ao X25519, nunca o
368
+ para um adversário quântico futuro. Ele é _somado_ ao X25519, nunca o
368
369
  substitui — a segurança é no mínimo a clássica. O `/trustlist` mostra `[PQ]`.
369
370
  - **Salas privadas** nunca enviam a senha a lugar nenhum: ela deriva uma chave
370
371
  Ed25519 (Argon2id) que responde a um desafio do servidor, e o conteúdo da
package/docs/PLUGINS.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # CipherMesh Plugin API
2
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.
3
+ CipherMesh loads user plugins from `~/.ciphermesh/plugins/*.js` and routes
4
+ unknown slash-commands to them. Plugins work in both relay and P2P mode.
5
+
6
+ **Nothing there runs until you say so.** A file in that directory is found, not
7
+ loaded. Run `/plugins` to see what is waiting and `/plugins allow <file>` to
8
+ approve it — the approval is remembered in `~/.ciphermesh/config.json` under
9
+ `pluginsAllowed`, so you are asked once per file.
5
10
 
6
11
  ## Quick start
7
12
 
@@ -10,8 +15,15 @@ mkdir -p ~/.ciphermesh/plugins
10
15
  cp examples/plugins/roll.js examples/plugins/poll.js ~/.ciphermesh/plugins/
11
16
  ```
12
17
 
13
- Restart the client — `/plugins` lists what loaded, and `/roll 2d20+3` /
14
- `/poll Pizza tonight? | yes | obviously` just work.
18
+ Then in the client:
19
+
20
+ ```
21
+ /plugins → shows roll.js and poll.js waiting
22
+ /plugins allow roll → approved, loaded, and remembered
23
+ /plugins allow poll
24
+ ```
25
+
26
+ Now `/roll 2d20+3` and `/poll Pizza tonight? | yes | obviously` work.
15
27
 
16
28
  ## Plugin format
17
29
 
@@ -19,8 +31,8 @@ A plugin is an ES module whose **default export** is:
19
31
 
20
32
  ```js
21
33
  export default {
22
- name: 'roll', // required, unique
23
- description: 'Roll dice', // optional, shown by /plugins
34
+ name: 'roll', // required, unique
35
+ description: 'Roll dice', // optional, shown by /plugins
24
36
  commands: {
25
37
  // key = command name (with or without the leading slash)
26
38
  roll(args) {
@@ -31,16 +43,18 @@ export default {
31
43
  };
32
44
  ```
33
45
 
34
- Files that fail to import, or lack `name`/`commands`, are skipped silently.
46
+ A file that fails to import, or lacks `name`/`commands`, is skipped — but if you
47
+ approved it, `/plugins` says so. A plugin you asked for and did not get should
48
+ not disappear without a word.
35
49
 
36
50
  ## Handler return values
37
51
 
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*. |
52
+ | Return | Effect |
53
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
54
+ | `{ 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. |
55
+ | `{ info: '<text>' }` | Shown **only locally** as an info line. |
56
+ | `'<text>'` (plain string) | Same as `{ info }` — the original API, still supported. |
57
+ | `null` / `undefined` / throw | Treated as "not handled": the user sees _Unknown command_. |
44
58
 
45
59
  Handlers are synchronous — return the final value directly.
46
60
 
@@ -54,13 +68,38 @@ directory listing).
54
68
  ## Security model — read this
55
69
 
56
70
  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.
71
+ your privileges and full access to your keys in memory. There is no sandbox,
72
+ and approving one does not create one.
58
73
 
59
74
  - Only install plugins you wrote or read line-by-line.
60
75
  - Treat a plugin file like you treat `curl | sh`.
61
76
  - Plugins are never synced, auto-updated or downloaded by CipherMesh — the
62
77
  only way code gets into `~/.ciphermesh/plugins/` is you putting it there.
63
78
 
79
+ ### What approval actually buys
80
+
81
+ Before, any `.js` file appearing in that directory ran at the next start. The
82
+ warning above protected only the people who had already read it, and anything
83
+ able to write one file into a known path had code execution.
84
+
85
+ Now the file is listed and left alone until you approve it. That is the whole
86
+ guarantee, and it is worth being precise about its edges:
87
+
88
+ - **It is a consent step, not a sandbox.** An approved plugin can do everything
89
+ the client can do. Approve for the same reasons you would run a script.
90
+ - **Approval is per _file name_, not per plugin name.** That is forced, not
91
+ chosen: a plugin's own `name` lives inside the module, and reading it means
92
+ importing the module, and importing it is already running it. The check has
93
+ to work from the directory listing alone.
94
+ - **Replacing an approved file is not a new decision.** `roll.js` stays
95
+ approved even if its contents change completely. If you did not put the new
96
+ contents there, you have a bigger problem than plugins — but do not read the
97
+ approval as a promise about what the file contains.
98
+
99
+ There is no capability system, deliberately. Declaring what a plugin may do
100
+ without being able to enforce it would make the risk look bounded when it is
101
+ not, which is worse than the plain warning above.
102
+
64
103
  ## Included examples
65
104
 
66
105
  - [`examples/plugins/roll.js`](../examples/plugins/roll.js) — dice roller,