ciphermesh
v2.9.0
Published
Secure terminal chat for the local network (LAN) with real end-to-end encryption (E2EE) using libsodium
Maintainers
Readme
██████╗██╗██████╗ ██╗ ██╗███████╗██████╗ ███╗ ███╗███████╗███████╗██╗ ██╗
██╔════╝██║██╔══██╗██║ ██║██╔════╝██╔══██╗████╗ ████║██╔════╝██╔════╝██║ ██║
██║ ██║██████╔╝███████║█████╗ ██████╔╝██╔████╔██║█████╗ ███████╗███████║
██║ ██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║
╚██████╗██║██║ ██║ ██║███████╗██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║
╚═════╝╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝Chat de terminal com criptografia ponta-a-ponta — o servidor não lê uma palavra.
ciphermesh.de — o site, e o hub público em que qualquer pessoa pode entrar.
🇺🇸 Read in English · Guia de Setup · Arquitetura · Política de Segurança · Termos do Hub · Como contribuir
Você ──[payload cifrado]──▶ Relay (cego) ──[payload cifrado]──▶ Amigo
Curve25519 + XSalsa20-Poly1305 · Double Ratchet · zero-knowledgeCipherMesh é um chat de terminal onde a criptografia é o produto. As chaves vivem em páginas de memória travadas, cada mensagem usa uma chave nova do ratchet, e o servidor de relay só enxerga ciphertext — não lê, não altera, não forja nada. Funciona na sua LAN de cara, e pela internet com Tailscale (sem port forwarding, imune a CGNAT).
✨ Destaques
| | Feature | Resumo |
| --- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 🔐 | E2EE de verdade | Curve25519 + XSalsa20-Poly1305 via libsodium, chaves em sodium_malloc — nunca tocam o disco |
| 🔄 | Perfect Forward Secrecy | Double Ratchet: uma chave por mensagem — comprometer hoje ≠ ler ontem |
| 🛡️ | 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) |
| 🕶️ | 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) |
| 🕵️ | 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 |
| 🌐 | LAN e internet | Detecta Tailscale sozinho e mostra o endereço alcançável no banner |
| 📨 | Convites com QR | /invite gera uma string ciphermesh:// + QR — colou, caiu na sala certa |
| ✓✓ | Read receipts cifrados | O ✓✓ viaja como ciphertext comum — o servidor não distingue de mensagem |
| 🗂️ | Histórico local cifrado | Opt-in (só com passphrase), Argon2id + XSalsa20-Poly1305, /search e /export |
| 🖼️ | Preview de imagens | Fotos recebidas renderizam no chat em half-blocks coloridos |
| 📎 | Transferências com resume | Chunks perdidos são re-pedidos; reconexão retoma de onde parou |
| 💬 | Cara de app moderno | Suas mensagens à direita, avatar de emoji por usuário, reply com citação, :fire: → 🔥 |
| 🎞️ | 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 ↓" |
| 👻 | Deniable e efêmeras | Modo de negação plausível (crypto simétrica); mensagens efêmeras queimam caractere a caractere ao expirar |
| 🔒 | 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 |
| 🗂️ | 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 |
| 🩺 | 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 |
| 🔐 | 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 |
| 🛰️ | Modo P2P sem servidor | Descoberta de peers via mDNS na LAN — sem relay nenhum, e com quase o mesmo conjunto de comandos |
| 🧩 | Plugins | Solta um arquivo JS em ~/.ciphermesh/plugins e ganha comandos novos — exemplos /roll e /poll inclusos (API de plugins) |
🚀 Começando
Converse com alguém em menos de um minuto — sem instalar, sem conta, sem servidor próprio:
npx ciphermesh@latestNo prompt Server, digite ciphermesh.de — um relay público que qualquer
pessoa pode usar. Combine um nome de sala com quem você quer conversar
(/join nossasala) e pronto: vocês estão conversando com criptografia
ponta-a-ponta.
O hub é mantido como projeto pessoal sob estes termos. É um relay zero-knowledge: quem o opera não consegue ler suas mensagens — isso é uma propriedade do software, não uma promessa. Prefere o seu próprio? Todos os comandos abaixo funcionam igual num relay que você hospeda.
Outros modos:
npx ciphermesh server # rode o seu próprio relay
npx ciphermesh p2p # sem servidor, descoberta por mDNS na LANmacOS/Linux com Homebrew (veja Formula/ciphermesh.rb):
brew tap felipekreulich/ciphermesh
brew install ciphermeshOu pelo código-fonte:
git clone https://github.com/FelipeKreulich/secret-chat-lan.git
cd secret-chat-lan
npm installQuem hospeda (uma máquina roda o relay):
npm run server # ou: docker compose up -d | npx ciphermesh serverPrefere imagem pronta? Baixe o relay do GHCR (publicado a cada release):
docker run -p 3600:3600 ghcr.io/felipekreulich/secret-chat-lan:latestVai hospedar publicamente? A pasta deploy/ tem um
setup com Caddy + Docker que pega certificado Let's Encrypt sozinho — aí os
clientes verificam o servidor contra uma CA de verdade (sem janela de
trust-on-first-use), e um host que já apresentou certificado válido nunca pode
ser rebaixado silenciosamente para um self-signed.
Sem Node nenhum? Binários standalone para macOS e Linux (arm64/x64) — baixe
do
último release,
chmod +x, rode. Nada para instalar, nem Node.
Pegue os binários do release mais recente. Os publicados antes da v2.7.2 nunca embutiram o addon nativo e só rodavam na máquina que os construiu, então aqueles anexos foram removidos.
| Binário | O que é |
| -------------------------------- | ------------------------------------------------------------------------------------------ |
| ciphermesh-<plataforma> | Tudo: cliente, relay e P2P. ciphermesh server e ciphermesh p2p funcionam igual ao npm. |
| ciphermesh-server-<plataforma> | Só o relay, para quem hospeda e não quer mais nada na máquina. |
Todo mundo (incluindo quem hospeda):
npm run client # nickname → passphrase (opcional) → endereço do servidorNa mesma rede, use o IP da LAN que aparece no banner do servidor
(192.168.x.x:3600). Pela internet, instalem Tailscale
dos dois lados e usem o endereço Internet do banner — passo a passo completo
em docs/SETUP.md.
Já está no chat? Rode /invite <seu-ip>:3600 e mande a string (ou o QR) pra
quem você quiser puxar pra conversa.
Sem servidor nenhum? npm run p2p — os peers se encontram via mDNS. O modo
P2P agora fala quase o mesmo conjunto de comandos do cliente relay (presença,
/lock, /contacts, /mentions, /topic, histórico cifrado e mais); só
/invite, /create e /nick são específicos do relay por natureza.
🌐 A comunidade fica no ciphermesh.de
Durante muito tempo, usar o CipherMesh exigia já conhecer alguém que rodasse um relay. Isso acabou: ciphermesh.de é um hub público, gratuito, aberto a qualquer pessoa e sempre no ar. Ninguém precisa combinar quem vai subir o servidor.
npx ciphermesh@latest
# Server: ciphermesh.de
# Room: general- A
generalé a porta de entrada. É a sala padrão — conecte e diga oi se você só quer ver quem está por perto. - Qualquer sala, aberta por qualquer um. Escolha um nome, avise quem você quer lá dentro e comece a conversar. Sem conta, sem convite, sem lista de espera.
- Privada na hora que você quiser. Ponha uma senha e a sala é sua. A senha nunca chega ao servidor — ele guarda só um verificador derivado com Argon2id, que não dá para reverter.
- Nada sobrevive à conversa. A sala existe enquanto alguém está dentro dela. Quando a última pessoa sai, a sala e tudo o que foi dito nela desaparecem.
Manter o hub não transforma quem o opera em leitor: ele é zero-knowledge como qualquer outro relay do CipherMesh — a única diferença é que este nunca dorme. O uso é regido pelos termos.
Para chamar gente: /invite imprime uma string de entrada e um QR code, e
/rooms mostra o que está no ar.
Para que o hub existe. Ele existe para qualquer pessoa poder experimentar o CipherMesh e encontrar outras pessoas que o usam sem precisar subir um servidor antes. É um ponto de encontro, não um serviço de comunicação de uso geral — as salas não são casa de ninguém, não há contas, e nada fica entre sessões.
Se você precisa de um relay que seja seu, suba um. Ele responde a você, não
depende da disponibilidade de mais ninguém, e não faz suas conversas passarem por
uma máquina administrada por um estranho. A pasta deploy/
tem o setup Docker pronto. Para o que é importante essa é a melhor resposta — e é
aquela para a qual este software foi feito.
💬 Comandos
| Comando | Descrição |
| ----------------------------- | ----------------------------------------------------------------------- |
| /help | Todos os comandos |
| /tips | Mostra uma dica rotativa de segurança/UX |
| /users | Quem está online (com away/status) |
| /msg <nick> <texto> | Mensagem privada (DM) |
| /reply <texto> | Responde citando a última mensagem recebida |
| /me <ação> | Ação em terceira pessoa — «felipe está compilando» |
| /watch [add\|remove\|clear] | Alerta quando uma palavra aparece em qualquer sala, como uma menção |
| /invite [host:porta] | Gera convite ciphermesh:// + QR code |
| /nick <novo> | Troca de apelido (antes de entrar — recupera de "apelido em uso") |
| /quit | Sair |
| Comando | Descrição |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| /join <sala> [senha] | Abre a sala como um novo buffer — você continua nas outras (estilo IRC) |
| /leave [sala] | Sai de uma sala; o buffer fecha (a última sala é protegida) |
| /create <sala> <senha> | Cria uma sala privada 🔒 — veja abaixo |
| /rooms | Lista salas (🔒 marca as privadas) |
| /room | Sala atual + sua lista de buffers |
| /topic [texto\|clear] | Mostra ou define o assunto da sala — aparece na barra de status e é sincronizado para quem entra depois |
| /owner | Dono da sala |
| /kick /mute /ban | Moderação (dono da sala) — presa à chave pública, então trocar de apelido não desfaz um ban |
| /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. |
Buffers: esteja em várias salas ao mesmo tempo — Alt+1..9 alterna, e a
barra de status mostra [1:general] [2:dev •3] com não-lidas por sala. Como o
relay é cego (sealed sender), a qual sala cada mensagem pertence viaja dentro
do payload cifrado — o servidor nunca fica sabendo.
Salas privadas são zero-knowledge: a senha nunca sai da sua máquina. Ao entrar, o cliente deriva uma chave Ed25519 da senha (Argon2id) e responde um desafio do servidor com uma assinatura — o servidor guarda só um verificador, em memória, que morre quando a última pessoa sai (salas são sempre efêmeras). Além disso, tudo que é dito numa sala privada carrega uma camada simétrica extra derivada da senha: mesmo um relay malicioso que deixasse alguém entrar sem verificar não leria uma palavra. Combine a senha por outro canal.
| Comando | Descrição |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| /fingerprint [nick] | Fingerprint + um randomart determinístico da chave |
| /verify <nick> | Código SAS (~40 bits) + QR + randomart da chave para verificar |
| /verify-confirm <nick> | Marca o peer como verificado |
| /backup [caminho] | Backup cifrado da identidade + peers verificados (restaura no startup) |
| /trust <nick> / /trustlist | Aceita chave nova / status de confiança |
| /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 |
| /deniable [on\|off] | Modo de negação plausível |
| /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 |
| /panic [sim] | Wipe de coação — apaga com segurança todos os segredos do disco (sessão, histórico, confiança, chaves) e sai |
| /cover [on\|constant\|off] | Cover traffic — on = iscas com jitter, constant = canal de taxa constante |
| /theme [nome] | Tema de cores dos nicks: neon, matrix, mono, sunset, ocean |
| /ephemeral <30s\|5m\|1h\|off> | Mensagens autodestrutivas |
| /receipts [on\|off] | Envio de confirmação de leitura (✓✓) |
| /audit [n] | Log de auditoria local |
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.
| Comando | Descrição |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| /file <caminho> | Oferece arquivo (≤ 50MB) — o destinatário precisa dar /accept; retoma |
| /voice [seg] | Grava e envia nota de voz cifrada (precisa de sox/ffmpeg; default 10s) |
| /play [caminho] | Toca a última nota de voz recebida (afplay/sox/ffplay) |
| /accept [id] / /reject [id] | Aceita / recusa uma oferta de arquivo recebida |
| /img [caminho] | Renderiza a última imagem recebida em alta resolução (kitty/iTerm2) |
| /retention <7d\|24h\|30m> | Purga o histórico local mais antigo que o tempo dado |
| /search <termo> | Busca no histórico local cifrado (em disco, entre sessões) |
| /find [termo] — Ctrl+F | Busca no histórico da sala na tela e, com Enter, salta para a mensagem destacada |
| /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 |
| /history [n] | Últimas n mensagens do histórico |
| /export [caminho] | Exporta o histórico em .txt ou .json (texto plano!) |
| Comando | Descrição |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| /away [motivo] / /back | Marca/remove ausência — enquanto ausente, não-lidas são contadas ([away · N new]) e o /back mostra um resumo |
| /mentions [n] | Menções recentes a você na sessão (quem, onde, quando) |
| /status <texto\|off> | Status livre — emoji à vontade (/status :fire: codando) |
| /react <emoji> | Reage à última mensagem — o emoji aparece na própria mensagem, com contagem quando várias pessoas reagem |
| /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 |
| /pin /unpin /pins | Fixa mensagens |
| /sound /notify | Notificações sonoras / desktop |
| /dnd [on\|off\|mentions\|HH:MM-HH:MM] | Não perturbe, só menções, ou horário silencioso |
| /clear | Limpa o chat |
Digitar :fire: em qualquer lugar vira 🔥 (Tab autocompleta shortcodes).
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.
Primeira execução & arquivo de config
Na primeiríssima execução, um wizard de 30 segundos te guia por nickname,
tema de cores e servidor padrão (com um mini-curso de 3 linhas de como a
criptografia funciona) e salva tudo — das próximas vezes você cai direto no
chat. Refaça quando quiser com ciphermesh --setup; pule em scripts/CI com
--no-onboard.
O CipherMesh também lembra a sua última sessão: o prompt de servidor passa
a ter como padrão onde você estava, e depois de conectar você volta sozinho
para a última sala (salas privadas ficam de fora — o nome delas nunca toca o
disco). Para começar do zero: ciphermesh --fresh.
O wizard grava o ~/.ciphermesh/config.json — que você também pode editar na
mão. Todas as chaves são opcionais (chaves desconhecidas são ignoradas):
{
"nickname": "felipe",
"server": "wss://100.x.y.z:3600",
"sound": false,
"notify": true,
"receipts": true,
"deniable": false,
"cover": "constant",
"theme": "matrix",
"autoAway": 10,
"autoLock": 5,
"dnd": "22:00-08:00"
}nickname/server pré-preenchem os prompts (Enter aceita); o resto é aplicado na inicialização como se você tivesse rodado o comando /sound, /cover, /theme, … correspondente. Temas: neon (padrão), matrix, mono, sunset, ocean.
🔒 Modelo de segurança
- O relay é zero-knowledge: roteia ciphertext com padding anti-metadados e nada mais. Read receipts, reações, presença — tudo é ciphertext indistinguível pro servidor.
- Resistência a análise de tráfego: todo ciphertext é paddado até buckets
de tamanho fixo (o relay não lê o comprimento da mensagem); chunks de arquivo
são paddados a um tamanho uniforme, então o tamanho exato do arquivo também
não vaza.
/cover onadiciona iscas com jitter e/cover constantfaz suas mensagens saírem por um canal de taxa constante (iscas preenchem os slots ociosos), pra ele não distinguir conversa ativa de ociosa. Iscas são descartadas em silêncio. - Anti-replay com nonces monotônicos, rotação de chaves a cada hora com
janela de graça, limpeza segura de memória (
sodium_memzero) após o uso. - Wipe de coação (
/panic sim): sobrescreve e apaga todos os segredos do disco (estado da sessão, histórico, confiança, auditoria), zera as chaves em memória e sai sem salvar — para um device perdido ou apreendido. - Estado de sessão e histórico local são cifrados em repouso com Argon2id + XSalsa20-Poly1305 — sem passphrase, nada persiste.
- Pós-quântico híbrido: cada sessão mistura um segredo ML-KEM-768 na raiz
do ratchet na inicialização, então tráfego gravado hoje continua ilegível
para um adversário quântico futuro. Ele é somado ao X25519, nunca o
substitui — a segurança é no mínimo a clássica. O
/trustlistmostra[PQ]. - Salas privadas nunca enviam a senha a lugar nenhum: ela deriva uma chave Ed25519 (Argon2id) que responde a um desafio do servidor, e o conteúdo da sala carrega uma camada simétrica extra — um relay malicioso que deixasse alguém entrar sem verificar ainda assim não leria uma palavra.
- Análise de ameaças e detalhes do protocolo: docs/ARCHITECTURE.md. Achou algo? Veja SECURITY.md.
🧪 Desenvolvimento
npm run server:dev # relay com auto-reload
npm test # 418 testes (crypto, ratchet, fuzz, controllers, transferências…)
npm run validate # lint + prettier + testes — o mesmo que o CI rodaO CI roda em todo push/PR (Node 20 e 22). Tags v* disparam testes + GitHub
Release automaticamente.
📄 Licença
MIT — faça coisas boas com isso.
