La saga della firma in sandbox su macOS (ovvero perché esistono le versioni dalla 1.4.0 alla 1.4.5)
Se oggi hai guardato il nostro changelog e hai notato sei rilasci di patch in un solo giorno, forse ti stai chiedendo che cosa sia successo. La risposta breve: la firma del codice su macOS e l'App Sandbox ci hanno quasi steso. Ecco la storia completa.
Come funziona MailVault sotto il cofano
MailVault è un'app desktop Tauri v2. Il frontend è React e gira in una webview nativa, mentre il backend è un server Node.js Express compilato in un binario nativo con Bun. Questo binario (chiamato "sidecar") gira insieme all'app principale e gestisce tutta la comunicazione IMAP/SMTP.
Per la distribuzione su macOS, Apple richiede che le app siano firmate con un certificato Developer ID e notarizzate. Abilitiamo anche l'App Sandbox — il livello di sicurezza di Apple che limita ciò a cui l'app può accedere sul tuo sistema. È obbligatorio per il Mac App Store ed è buona pratica per qualsiasi app desktop.
Il problema
Dopo il rilascio della v1.4.0 abbiamo scoperto che il server sidecar non partiva nelle build di release. L'app si avviava senza problemi, ma il backend crashava in silenzio con codice di uscita 133 (SIGTRAP). In sviluppo funzionava tutto perfettamente — era colpita solo la build di release, firmata e in sandbox.
Ci sono volute diverse iterazioni per togliere gli strati:
- v1.4.0 — Il server si metteva in ascolto su
0.0.0.0ma il frontend si collegava alocalhost, con un disallineamento IPv4/IPv6. Risolto mettendosi in ascolto esplicitamente su127.0.0.1. - v1.4.1 — La finestra "Aggiungi account" usava una
fetch('/api/test-connection')grezza invece dell'helper dell'API. Nelle build di release di Tauri l'origine della webview ètauri://localhost, nonhttp://localhost— quindi gli URL relativi finiscono nel posto sbagliato. - v1.4.2 — Mancava l'entitlement
com.apple.security.network.server. La sandbox impediva al sidecar di mettersi in ascolto su una porta. - v1.4.3 — Avevamo una pulizia dei processi basata sulla shell (
lsof,kill) che dentro l'App Sandbox è bloccata. Sostituita con l'handleCommandChilddi Tauri. - v1.4.4 — Il vero colpevole: i binari compilati con Bun hanno bisogno del JIT (compilazione Just-In-Time). Il compilatore JIT alloca memoria eseguibile a runtime. macOS ha un entitlement che lo consente (
com.apple.security.cs.allow-jit), ma Tauri firma tutti i binari del bundle dell'app con gli stessi entitlement, usando--deep. Il sidecar si ritrovava con gli entitlement dell'App Sandbox, che bloccavano il JIT anche in presenza del flag allow-jit. - v1.4.5 — Sistemata la catena di firma. Ora il sidecar viene firmato a parte, con i propri entitlement (niente App Sandbox, JIT consentito). L'app principale mantiene l'App Sandbox. L'ordine di firma conta: prima il sidecar, poi il bundle dell'app principale senza
--deep, per preservare gli entitlement del sidecar. Abbiamo anche scoperto che la nostra pipeline di CI caricava il DMG non firmato generato automaticamente da Tauri invece del nostro, firmato come si deve.
La soluzione
La soluzione ha due parti:
- Entitlement separati — Il sidecar riceve
entitlements-sidecar.plist(JIT + rete, niente sandbox). L'app principale riceveentitlements.plist(sandbox + JIT + rete). - Ordine di firma corretto — Firma prima il sidecar con i suoi entitlement, poi firma il bundle
.appprincipale senza--deep. Usare--deeprifirmerebbe ricorsivamente tutto quello che c'è dentro il bundle, sovrascrivendo gli entitlement del sidecar.
Come verificare la firma da solo
Se vuoi verificare che MailVault sia firmato e notarizzato correttamente sulla tua macchina, apri il Terminale ed esegui questi comandi:
1. Verifica la firma dell'app:
codesign --verify --verbose /Applications/MailVault.app
Dovresti vedere:
/Applications/MailVault.app: valid on disk /Applications/MailVault.app: satisfies its Designated Requirement
2. Controlla gli entitlement dell'app principale:
codesign -d --entitlements - /Applications/MailVault.app
Cerca com.apple.security.app-sandbox impostato su true — conferma che l'app gira in una sandbox.
3. Controlla gli entitlement del sidecar:
codesign -d --entitlements - /Applications/MailVault.app/Contents/MacOS/mailvault-server
Il sidecar deve avere com.apple.security.cs.allow-jit ma non deve avere com.apple.security.app-sandbox.
4. Verifica la notarizzazione:
spctl --assess --type execute -v /Applications/MailVault.app
Dovresti vedere:
/Applications/MailVault.app: accepted source=Notarized Developer ID
5. Controlla la notarizzazione del DMG (prima di installarlo):
spctl --assess --type open --context context:primary-signature -v ~/Downloads/MailVault-v1.4.5.dmg
Lezioni imparate
- Prova presto le build di release. Le build di sviluppo saltano del tutto la firma del codice e la sandbox. Lo scarto tra "funziona in sviluppo" e "funziona in release" può essere enorme.
- I sidecar richiedono un trattamento a parte. Se dentro un'app Tauri metti un binario compilato con Bun, Node o Deno, gli servono entitlement propri. Tauri non supporta nativamente entitlement per singolo binario — devi rifirmare dopo la build.
--deepè pericoloso. È comodo, ma sovrascrive tutte le firme annidate con gli stessi entitlement. Se binari diversi hanno bisogno di entitlement diversi, firmali uno per uno.- Un solo script di build per il locale e per la CI. Avere una logica di firma separata per le build locali e per la CI ha portato la CI a caricare il DMG sbagliato, quello non firmato. Ora usano entrambe lo stesso script.
Se sei stato colpito dalle release rotte, ci scusiamo per il disagio. La v1.4.5 è la release stabile e firmata correttamente. Le tue email non sono mai state a rischio — il problema riguardava solo il binario dell'app che non avviava il suo server backend.