Saltar al contenido
Volver al blog

La saga de la firma en el sandbox de macOS (o por qué existen las versiones de la 1.4.0 a la 1.4.5)

5 min de lectura

Si hoy has mirado nuestro changelog y has visto seis versiones de parche en un mismo día, quizá te preguntes qué ha pasado. La respuesta corta: la firma de código y el App Sandbox de macOS casi acaban con nosotros. Aquí va la historia completa.

Cómo funciona MailVault por dentro

MailVault es una app de escritorio hecha con Tauri v2. El frontend es React ejecutándose en una webview nativa, y el backend es un servidor Express de Node.js compilado a binario nativo con Bun. Ese binario (lo llamamos "sidecar") se ejecuta junto a la app principal y se encarga de toda la comunicación IMAP/SMTP.

Para distribuir en macOS, Apple exige que las apps estén firmadas con un certificado Developer ID y notarizadas. Además activamos el App Sandbox — la capa de seguridad de Apple que restringe a qué puede acceder la app en tu sistema. Es obligatorio para la Mac App Store y una buena práctica en cualquier app de escritorio.

El problema

Tras publicar la v1.4.0, descubrimos que el servidor sidecar no arrancaba en las compilaciones de publicación. La app abría bien, pero el backend se caía en silencio con el código de salida 133 (SIGTRAP). Todo funcionaba perfecto en desarrollo: solo se veía afectada la compilación firmada y con sandbox.

Hicieron falta varias iteraciones para ir pelando las capas:

  • v1.4.0 — El servidor escuchaba en 0.0.0.0 pero el frontend se conectaba a localhost, con el consiguiente desajuste entre IPv4 e IPv6. Se resolvió escuchando explícitamente en 127.0.0.1.
  • v1.4.1 — La ventana de "Añadir cuenta" usaba un fetch('/api/test-connection') directo en lugar del helper de la API. En las compilaciones de publicación de Tauri, el origen de la webview es tauri://localhost, no http://localhost — así que las URL relativas apuntan al sitio equivocado.
  • v1.4.2 — Faltaba el permiso com.apple.security.network.server. El sandbox impedía que el sidecar escuchara en un puerto.
  • v1.4.3 — Teníamos una limpieza de procesos basada en la shell (lsof, kill) que el App Sandbox bloquea. La sustituimos por el handle CommandChild de Tauri.
  • v1.4.4 — El culpable de verdad: los binarios compilados con Bun necesitan JIT (compilación al vuelo). El compilador JIT reserva memoria ejecutable en tiempo de ejecución. macOS tiene permisos para autorizarlo (com.apple.security.cs.allow-jit), pero Tauri firma todos los binarios del paquete de la app con los mismos permisos usando --deep. El sidecar acababa con los permisos del App Sandbox, que bloqueaban el JIT aunque estuviera presente el flag allow-jit.
  • v1.4.5 — Arreglamos la cadena de firma. Ahora el sidecar se firma por separado con sus propios permisos (sin App Sandbox y con JIT permitido). La app principal conserva el App Sandbox. El orden de firma importa: primero el sidecar y después el paquete de la app principal sin --deep, para preservar los permisos del sidecar. También descubrimos que nuestra cadena de CI subía el DMG sin firmar que genera Tauri automáticamente, en lugar del nuestro correctamente firmado.

El arreglo

La solución tiene dos partes:

  1. Permisos separados — El sidecar recibe entitlements-sidecar.plist (JIT + red, sin sandbox). La app principal recibe entitlements.plist (sandbox + JIT + red).
  2. Orden de firma correcto — Firma primero el sidecar con sus propios permisos y después el paquete .app principal sin --deep. Con --deep se volvería a firmar recursivamente todo lo que hay dentro del paquete, sobrescribiendo los permisos del sidecar.

Cómo verificar la firma tú mismo

Si quieres comprobar que MailVault está bien firmado y notarizado en tu máquina, abre el Terminal y ejecuta estos comandos:

1. Verifica la firma de la app:

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

Deberías ver:

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

2. Comprueba los permisos de la app principal:

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

Busca com.apple.security.app-sandbox con valor true — eso confirma que la app se ejecuta en un sandbox.

3. Comprueba los permisos del sidecar:

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

El sidecar debe tener com.apple.security.cs.allow-jit, pero no debe tener com.apple.security.app-sandbox.

4. Verifica la notarización:

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

Deberías ver:

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

5. Comprueba la notarización del DMG (antes de instalar):

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

Lecciones aprendidas

  • Prueba pronto las compilaciones de publicación. Las compilaciones de desarrollo se saltan por completo la firma de código y el sandbox. La distancia entre "funciona en desarrollo" y "funciona en publicación" puede ser enorme.
  • Los sidecars requieren un trato especial. Si empaquetas dentro de una app de Tauri un binario compilado con Bun, Node o Deno, necesita sus propios permisos. Tauri no admite permisos por binario de forma nativa — hay que volver a firmar después de compilar.
  • --deep es peligroso. Es cómodo, pero sobrescribe todas las firmas anidadas con los mismos permisos. Si distintos binarios necesitan permisos distintos, fírmalos uno a uno.
  • Un solo script de compilación para local y para CI. Tener una lógica de firma distinta para las compilaciones locales y para la CI hizo que la CI subiera el DMG equivocado, el que no estaba firmado. Ahora las dos usan el mismo script.

Si te afectaron las versiones rotas, sentimos las molestias. La v1.4.5 es la versión estable y firmada correctamente. Tus correos nunca corrieron peligro: el problema era solo que el binario de la app no arrancaba su servidor backend.