Zum Inhalt springen
Zurück zum Blog

Die macOS-Sandbox-Signatur-Saga (oder warum es v1.4.0 bis v1.4.5 gibt)

5 Min. Lesezeit

Wenn du heute in unser Changelog geschaut und dort sechs Patch-Releases an einem einzigen Tag bemerkt hast, fragst du dich vielleicht, was passiert ist. Die kurze Antwort: Code-Signierung und App Sandbox unter macOS haben uns fast zerlegt. Hier ist die ganze Geschichte.

Wie MailVault unter der Haube funktioniert

MailVault ist eine Desktop-App auf Basis von Tauri v2. Das Frontend ist React in einem nativen Webview, das Backend ein Node.js-Express-Server, der mit Bun zu einem nativen Binary kompiliert wird. Dieses Binary (ein „Sidecar“) läuft neben der Haupt-App und wickelt die gesamte IMAP-/SMTP-Kommunikation ab.

Für den Vertrieb unter macOS verlangt Apple, dass Apps mit einem Developer-ID-Zertifikat signiert und notarisiert sind. Wir aktivieren zusätzlich die App Sandbox — Apples Sicherheitsschicht, die einschränkt, worauf die App auf deinem System zugreifen darf. Für den Mac App Store ist sie Pflicht, und für jede Desktop-App ist sie gute Praxis.

Das Problem

Nach dem Release von v1.4.0 stellten wir fest, dass der Sidecar-Server in Release-Builds nicht startete. Die App startete problemlos, aber das Backend stürzte still mit Exit-Code 133 (SIGTRAP) ab. In der Entwicklung funktionierte alles einwandfrei — betroffen war nur der signierte, gesandboxte Release-Build.

Es brauchte mehrere Anläufe, die Schichten abzutragen:

  • v1.4.0 — Der Server lauschte auf 0.0.0.0, das Frontend verband sich aber mit localhost, was zu einer IPv4/IPv6-Diskrepanz führte. Behoben, indem explizit an 127.0.0.1 gebunden wird.
  • v1.4.1 — Das Dialogfeld „Konto hinzufügen“ benutzte ein rohes fetch('/api/test-connection') statt des API-Helfers. In Tauri-Release-Builds ist der Ursprung des Webviews tauri://localhost, nicht http://localhost — relative URLs landen also an der falschen Stelle.
  • v1.4.2 — Die Berechtigung com.apple.security.network.server fehlte. Die Sandbox hinderte den Sidecar daran, auf einem Port zu lauschen.
  • v1.4.3 — Wir hatten eine Prozessbereinigung über die Shell (lsof, kill), die innerhalb der App Sandbox blockiert ist. Ersetzt durch Tauris Handle CommandChild.
  • v1.4.4 — Der wahre Übeltäter: Von Bun kompilierte Binaries brauchen JIT (Just-in-Time-Kompilierung). Der JIT-Compiler reserviert zur Laufzeit ausführbaren Speicher. macOS hat zwar Berechtigungen, die das erlauben (com.apple.security.cs.allow-jit), aber Tauri signiert alle Binaries im App-Bundle mit denselben Berechtigungen, per --deep. So bekam der Sidecar App-Sandbox-Berechtigungen, die JIT selbst mit gesetztem allow-jit-Flag blockierten.
  • v1.4.5 — Die Signierstrecke repariert. Der Sidecar wird jetzt getrennt mit eigenen Berechtigungen signiert (keine App Sandbox, JIT erlaubt). Die Haupt-App behält die App Sandbox. Die Reihenfolge zählt: erst der Sidecar, dann das Bundle der Haupt-App ohne --deep, damit die Berechtigungen des Sidecars erhalten bleiben. Dabei fiel auch auf, dass unsere CI-Strecke das automatisch erzeugte, unsignierte DMG von Tauri hochlud statt unseres korrekt signierten.

Die Lösung

Die Lösung hat zwei Teile:

  1. Getrennte Berechtigungen — Der Sidecar bekommt entitlements-sidecar.plist (JIT + Netzwerk, keine Sandbox). Die Haupt-App bekommt entitlements.plist (Sandbox + JIT + Netzwerk).
  2. Richtige Signierreihenfolge — Zuerst den Sidecar mit seinen eigenen Berechtigungen signieren, dann das .app-Bundle der Haupt-App ohne --deep. Mit --deep würde alles im Bundle rekursiv neu signiert und die Berechtigungen des Sidecars überschrieben.

So prüfst du die Signatur selbst

Wenn du überprüfen willst, dass MailVault auf deinem Rechner korrekt signiert und notarisiert ist, öffne das Terminal und führe diese Befehle aus:

1. Die Signatur der App prüfen:

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

Du solltest sehen:

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

2. Die Entitlements der Haupt-App prüfen:

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

Achte darauf, dass com.apple.security.app-sandbox auf true steht — das bestätigt, dass die App in einer Sandbox läuft.

3. Die Entitlements des Sidecars prüfen:

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

Der Sidecar sollte com.apple.security.cs.allow-jit haben, aber nicht com.apple.security.app-sandbox.

4. Die Notarisierung prüfen:

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

Du solltest sehen:

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

5. Die Notarisierung des DMG prüfen (vor der Installation):

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

Gelernte Lektionen

  • Teste Release-Builds früh. Entwicklungs-Builds überspringen Code-Signierung und Sandboxing vollständig. Die Lücke zwischen „läuft in der Entwicklung“ und „läuft im Release“ kann riesig sein.
  • Sidecars brauchen eine Sonderbehandlung. Wenn du ein mit Bun/Node/Deno kompiliertes Binary in eine Tauri-App packst, braucht es eigene Berechtigungen. Tauri unterstützt Berechtigungen pro Binary nicht von Haus aus — du musst nach dem Build erneut signieren.
  • --deep ist gefährlich. Es ist bequem, überschreibt aber alle verschachtelten Signaturen mit denselben Berechtigungen. Wenn verschiedene Binaries verschiedene Berechtigungen brauchen, signiere sie einzeln.
  • Ein Build-Skript für lokal und CI. Getrennte Signierlogik für lokale Builds und für die CI führte dazu, dass die CI das falsche (unsignierte) DMG hochlud. Jetzt benutzen beide dasselbe Skript.

Wenn dich die kaputten Releases getroffen haben, entschuldigen wir uns für die Unannehmlichkeit. v1.4.5 ist das stabile, korrekt signierte Release. Deine E-Mails waren nie in Gefahr — es ging rein darum, dass das App-Binary seinen Backend-Server nicht startete.