Pular para o conteúdo
Voltar ao blog

A saga da assinatura no sandbox do macOS (ou por que existem as versões 1.4.0 até 1.4.5)

5 min de leitura

Se você olhou nosso changelog hoje e reparou em seis versões de correção em um único dia, deve estar se perguntando o que aconteceu. A resposta curta: a assinatura de código do macOS e o App Sandbox quase acabaram com a gente. Aqui vai a história completa.

Como o MailVault funciona por dentro

O MailVault é um aplicativo de desktop feito em Tauri v2. O frontend é React rodando em uma webview nativa e o backend é um servidor Node.js Express compilado como binário nativo com o Bun. Esse binário (chamado de "sidecar") roda ao lado do aplicativo principal e cuida de toda a comunicação IMAP/SMTP.

Para distribuir no macOS, a Apple exige que os aplicativos sejam assinados com um certificado Developer ID e notarizados. Nós também ativamos o App Sandbox — a camada de segurança da Apple que restringe o que o aplicativo pode acessar no seu sistema. Ele é obrigatório na Mac App Store e é uma boa prática para qualquer aplicativo de desktop.

O problema

Depois de lançar a v1.4.0, descobrimos que o servidor sidecar não iniciava nas builds de release. O aplicativo abria normalmente, mas o backend travava em silêncio com código de saída 133 (SIGTRAP). Em desenvolvimento tudo funcionava perfeitamente — só a build de release, assinada e em sandbox, era afetada.

Foram várias iterações até descascar as camadas:

  • v1.4.0 — O servidor escutava em 0.0.0.0, mas o frontend conectava em localhost, causando um descompasso entre IPv4 e IPv6. Resolvido escutando explicitamente em 127.0.0.1.
  • v1.4.1 — A janela de "Adicionar conta" usava um fetch('/api/test-connection') cru em vez do auxiliar de API. Nos builds de release do Tauri, a origem da webview é tauri://localhost, e não http://localhost — então as URLs relativas apontam para o lugar errado.
  • v1.4.2 — Faltava a permissão com.apple.security.network.server. O sandbox impedia o sidecar de escutar em uma porta.
  • v1.4.3 — Tínhamos uma limpeza de processos baseada em shell (lsof, kill) que é bloqueada dentro do App Sandbox. Substituída pelo CommandChild do Tauri.
  • v1.4.4 — O culpado de verdade: binários compilados com o Bun precisam de JIT (compilação just-in-time). O compilador JIT aloca memória executável em tempo de execução. O macOS tem permissões para liberar isso (com.apple.security.cs.allow-jit), mas o Tauri assina todos os binários do pacote do aplicativo com as mesmas permissões, usando --deep. O sidecar acabou ficando com as permissões do App Sandbox, o que bloqueava o JIT mesmo com a flag allow-jit presente.
  • v1.4.5 — Corrigimos o processo de assinatura. Agora o sidecar é assinado separadamente, com as suas próprias permissões (sem App Sandbox, com JIT liberado). O aplicativo principal mantém o App Sandbox. A ordem da assinatura importa: primeiro o sidecar, depois o pacote do aplicativo principal sem --deep, para preservar as permissões do sidecar. Também descobrimos que nosso processo de CI estava subindo o DMG não assinado gerado automaticamente pelo Tauri, em vez do nosso, devidamente assinado.

A correção

A solução tem duas partes:

  1. Permissões separadas — O sidecar recebe o entitlements-sidecar.plist (JIT + rede, sem sandbox). O aplicativo principal recebe o entitlements.plist (sandbox + JIT + rede).
  2. Ordem certa na assinatura — Assine primeiro o sidecar, com as permissões dele, e depois o pacote .app principal, sem --deep. Usar --deep reassinaria recursivamente tudo o que está dentro do pacote, sobrescrevendo as permissões do sidecar.

Como conferir a assinatura você mesmo

Se quiser verificar que o MailVault está corretamente assinado e notarizado na sua máquina, abra o Terminal e rode estes comandos:

1. Conferir a assinatura do aplicativo:

codesign --verify --verbose /Applications/MailVault.app

Você deve ver:

/Applications/MailVault.app: valid on disk
/Applications/MailVault.app: satisfies its Designated Requirement

2. Conferir os entitlements do aplicativo principal:

codesign -d --entitlements - /Applications/MailVault.app

Procure por com.apple.security.app-sandbox com o valor true — isso confirma que o aplicativo roda em um sandbox.

3. Conferir os entitlements do sidecar:

codesign -d --entitlements - /Applications/MailVault.app/Contents/MacOS/mailvault-server

O sidecar deve ter com.apple.security.cs.allow-jit, mas não deve ter com.apple.security.app-sandbox.

4. Conferir a notarização:

spctl --assess --type execute -v /Applications/MailVault.app

Você deve ver:

/Applications/MailVault.app: accepted
source=Notarized Developer ID

5. Conferir a notarização do DMG (antes de instalar):

spctl --assess --type open --context context:primary-signature -v ~/Downloads/MailVault-v1.4.5.dmg

Lições aprendidas

  • Teste os builds de release cedo. Os builds de desenvolvimento pulam totalmente a assinatura de código e o sandbox. A distância entre "funciona em dev" e "funciona em release" pode ser enorme.
  • Sidecars exigem tratamento especial. Se você empacota um binário compilado com Bun/Node/Deno dentro de um aplicativo Tauri, ele precisa das próprias permissões. O Tauri não tem suporte nativo a permissões por binário — é preciso reassinar depois do build.
  • --deep é perigoso. É conveniente, mas sobrescreve todas as assinaturas aninhadas com as mesmas permissões. Se binários diferentes precisam de permissões diferentes, assine cada um separadamente.
  • Um script de build para o local e para a CI. Ter lógicas de assinatura separadas para os builds locais e para a CI fez a CI subir o DMG errado (o não assinado). Agora os dois usam o mesmo script.

Se você foi afetado pelas versões quebradas, pedimos desculpas pelo transtorno. A v1.4.5 é a versão estável e corretamente assinada. Seus e-mails nunca correram risco — o problema era apenas o binário do aplicativo não conseguir iniciar o servidor de backend.