v1.16.0: la app de macOS por fin se firma de verdad
El build NUNCA firmó la app de macOS. Al no pasarle ninguna identidad, el bundler de Tauri se saltaba `codesign` entero y el .dmg salía con la firma ad hoc que deja el enlazador: Identifier=labstream_desktop-0a5819ba37119701 flags=0x20002(adhoc,linker-signed) Info.plist=not bound Sealed Resources=none Tres cosas mal, y ninguna avisaba en ningún log: · El Entitlements.plist del repo era DECORACIÓN. Los entitlements se escriben al firmar; sin firma, el micrófono, la cámara y el JIT nunca pidieron nada porque nunca estuvieron pedidos. · Ni el Info.plist ni los recursos iban dentro de la firma: se podían cambiar sin romperla. · El identificador era el del enlazador y no `co.labstream.os`, que es por el que macOS recuerda los permisos que le concedes a una app. AHORA. `APPLE_SIGNING_IDENTITY` se calcula en un paso propio y no en el `env:` del build, porque un secret que no existe llega como cadena VACÍA y Tauri lee `APPLE_CERTIFICATE=""` como «hay certificado» y revienta al importarlo. Solo se exporta lo que tiene valor: · con Developer ID → firma real, y notarizado si están las tres claves; · sin él → identidad "-", firma AD HOC. No engaña a Gatekeeper, pero sella los recursos, ata el Info.plist y APLICA LOS ENTITLEMENTS. En cuanto Jonathan añada los secrets, la siguiente etiqueta sale firmada de verdad sin tocar un solo archivo. ENSAYADO sobre una copia de la app instalada, con el mismo comando que va a correr el CI. Antes y después: flags=0x20002(adhoc,linker-signed) → flags=0x10002(adhoc,runtime) Identifier=labstream_desktop-0a58… → Identifier=co.labstream.os Info.plist=not bound → Info.plist entries=17 Sealed Resources=none → Sealed Resources version=2 rules=13 (sin entitlements) → allow-jit, audio-input, camera Y EL ENSAYO ENCONTRÓ UNA TRAMPA que `plutil -lint` no ve: el Entitlements.plist llevaba `--entitlements` dentro de un comentario XML, y dos guiones seguidos ahí son XML inválido. plutil lo aceptaba; AMFI, que es quien lo lee de verdad dentro de codesign, contestaba «Failed to parse entitlements» y la firma fallaba. Queda avisado en el propio archivo. De paso se cae `com.apple.security.network.client`: es exclusiva del App Sandbox y esta app no lo usa, así que no hacía nada. LA RED QUE FALTABA: un paso nuevo comprueba la firma del .app y TUMBA la publicación si los entitlements no llegaron. Que esto estuviera roto no lo dijo nada durante meses; se descubrió a mano persiguiendo otra cosa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Este commit está contenido en:
@@ -1,17 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!--
|
||||
ENTITLEMENTS DE macOS.
|
||||
|
||||
OJO AL ESCRIBIR AQUÍ: nada de dos guiones seguidos dentro de un comentario. XML los prohíbe,
|
||||
y el que lee este archivo NO es plutil (que los deja pasar) sino AMFI, dentro de codesign,
|
||||
que se planta con «Failed to parse entitlements». Por eso abajo las opciones de codesign se
|
||||
nombran sin sus guiones.
|
||||
|
||||
Estas claves solo sirven de algo si la app se FIRMA pasándole este archivo y con el hardened
|
||||
runtime puesto. Hasta el 2026-09-19 no era el caso: el build no firmaba nada y la app salía
|
||||
con la firma ad hoc del enlazador (flags=0x20002 adhoc linker-signed, Info.plist sin atar,
|
||||
recursos sin sellar). O sea, este archivo era decoración. Lo que lo arregla está en
|
||||
.github/workflows/build.yml.
|
||||
|
||||
Son entitlements del HARDENED RUNTIME, no del App Sandbox (esta app no usa sandbox). El
|
||||
hardened runtime cierra por defecto el micrófono, la cámara y la memoria ejecutable; cada
|
||||
clave vuelve a abrir una, y sin ella la función no falla con un error claro: simplemente no
|
||||
hay permiso.
|
||||
|
||||
Se cayó com.apple.security.network.client: esa es exclusiva del App Sandbox y, sin
|
||||
com.apple.security.app-sandbox, no hace absolutamente nada. Si algún día se mete la app en el
|
||||
sandbox, vuelve, junto con las de acceso a archivos que harán falta.
|
||||
-->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Micrófono: notas de voz del chat (getUserMedia en el webview). -->
|
||||
<!-- Micrófono: notas de voz del chat (getUserMedia en el webview). El texto que ve la
|
||||
persona al conceder el permiso vive en Info.plist (NSMicrophoneUsageDescription). -->
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<!-- Cámara: adjuntar foto/video en el chat. -->
|
||||
<!-- Cámara: adjuntar foto y video en el chat (NSCameraUsageDescription). -->
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<!-- Cliente de red (conecta con os.labstreamsas.com). -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<!-- Requerido por WKWebView (JIT de JavaScript) bajo hardened runtime. -->
|
||||
<!-- WKWebView compila JavaScript en caliente. Bajo hardened runtime eso es memoria
|
||||
ejecutable y hay que pedirlo explícitamente. -->
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
Referencia en una nueva incidencia
Block a user