Chega de Chatice do Keychain: Criptografia Opcional no Keychain do SO para Segredos Armazenados


Segunda-feira de manhã, você abre o app desktop do Hermes e, em vez da sua lista de chats, recebe um diálogo do macOS: “O Hermes quer acessar seu keychain. Digite sua senha para permitir.” Você digita, o app carrega. Terça-feira: o mesmo diálogo. Toda inicialização, para sempre — porque os segredos do app eram criptografados com uma chave estacionada no keychain de login, e seu keychain estava bloqueado, ausente ou corrompido. É o tipo de chatice que dá vontade de jogar a máquina pela janela. A v0.20.6 do Hermes (commit 6a6e16fa5d) resolve na raiz: a criptografia com keychain para segredos armazenados agora é um opt-in explícito, e o caminho padrão nunca chama o keychain.

A mudança atinge segredos armazenados no desktop: tokens de gateway remoto, cabeçalhos do Cloudflare Access e conjuntos de tokens OAuth nativos. Antes, o safeStorage do Electron estacionava uma chave por app (“Hermes Key”) no keychain de login do macOS, e qualquer toque no safeStorage — até a checagem “a criptografia está disponível?” — podia lançar um diálogo bloqueante de keychain em máquinas com keychain padrão bloqueado ou corrompido. Esse era um padrão inaceitável para um app de chat, então o comportamento mudou: a criptografia agora é opt-in, o armazenamento simples é o padrão, e uma migração única limpa a bagunça do comportamento antigo.

O que mudou

A política é definida num módulo standalone (electron/secret-storage-policy.ts) — deliberadamente livre de import 'electron' para que os unit tests rodem limpos — com três propriedades:

  • Opção DESLIGADA (padrão): os segredos são gravados com encoding 'plain' e nenhuma API do safeStorage é chamada nunca — incluindo isEncryptionAvailable(), que por si só toca no keychain. Sem keychain, sem diálogo, sem prompt.
  • Opção LIGADA: o comportamento anterior — criptografia estrita do safeStorage, falha barulhenta quando o keychain está indisponível e um diálogo de confirmação de texto simples a cada salvamento como saída de emergência.
  • Migração única: blobs legados gravados antes de a flag existir ficam codificados com safeStorage no disco. Com a opção desligada, o Hermes tenta uma passada de migração (descriptografar → regravar como simples). A passada é registrada no mesmo arquivo de configurações, tenha ou não sucesso, então um keychain quebrado custa no máximo um prompt na primeira inicialização após a atualização — nunca um por inicialização.

A flag on usa coerção estrita === true: um valor truthy mas não true não deve ativar silenciosamente os prompts de keychain (espelhando a regra de coerção allowPlainText do hardening.ts). O arquivo de política fica em secure-token-storage.json.

Como usar

Desktop (recomendado): abra Configurações → Gateway, encontre o toggle de keychain/armazenamento seguro e ligue-o. Ligar o toggle recodifica no lugar cada armazenamento de segredos — connection.json v1, connections.json v2 e native-oauth-tokens.json — e a spec de dados em repouso cobre as duas posturas: contrato inalterado quando optado, salvamentos padrão sem armazenamento seguro, permissões somente do dono e round-trip de reinicialização.

O detalhe da migração que vale saber: se você vem de uma versão mais antiga, a primeira inicialização após a atualização com a opção padrão (desligada) tenta a migração única — descriptografando blobs safeStorage existentes para arquivos simples 0600. Blobs indecifráveis (um keychain morto, por exemplo) são mantidos no disco, mas lidos como ausentes depois, classificados como ‘drop’, então um keychain morto gera prompt no máximo uma vez. No Linux, o backend subjacente de keychain é configurável: desktop.password_store aceita auto (detecta o keychain da sessão — KWallet via variáveis de ambiente da sessão KDE, GNOME Keyring / qualquer provider org.freedesktop.secrets como KeePassXC via D-Bus) ou um backend forçado (gnome-libsecret, kwallet, kwallet5, kwallet6); basic significa um armazenamento não criptografado. Uma variável de ambiente HERMES_DESKTOP_PASSWORD_STORE explícita continua vencendo a config.

O que permanece igual

  • Máquinas optadas mantêm o contrato completo: criptografia estrita do safeStorage, falha barulhenta quando o keychain está indisponível.
  • Permissões de arquivo somente do dono nos arquivos simples de fallback (0600) mantêm a postura de “não criptografado em repouso” razoável: legível apenas pelo seu usuário.
  • Segredos continuam sendo segredos. A mudança é sobre onde a chave de criptografia vive (keychain do SO vs. arquivos simples somente do dono), não sobre expor tokens em logs ou conteúdo visível ao modelo — a maquinaria de redação existente (security.redact_secrets) está intocada.

Quando optar

  • Opte se você quer criptografia em repouso em nível de SO e seu keychain está saudável — a postura padrão para laptops, máquinas compartilhadas ou setups com requisitos de compliance.
  • Deixe desligado se seu keychain está bloqueado/ausente/corrompido (a população dos diálogos), se você prefere não ter uma chave por app sentada no keychain de login, ou se o prompt a cada inicialização estava enlouquecendo você. O caminho padrão agora é livre de prompts por design.

O quadro maior

Isso faz parte do tema de confiabilidade da v0.20.6: a mesma janela que tornou padrões o cache de busca web e a compactação lean-tail também impediu o updater de matar a árvore de processos dos gateways (veja nosso guia de atualização graciosa). O armazenamento de segredos era a última coisa do desktop que podia bloquear sua manhã com um diálogo de senha — agora é opt-in, silencioso e reversível. Para o resumo completo da release, veja as notas da release v0.20.6.

Um toggle. Chega de “O Hermes quer acessar seu keychain” diário — a menos que você queira a criptografia, caso em que ela continua lá, a um switch de distância.