8 min de lectura

Cómo verificar un helper SSH antes de lanzarlo

Aprende a verificar un helper SSH comprobando su ruta en el bundle, firma, propietario, identidad de archivo y requisito al lanzarlo en macOS.

Cómo verificar un helper SSH antes de lanzarlo

Una pasarela local debe tratar su helper SSH como código privilegiado, aunque el helper no tenga una bóveda ni conserve estado. Recibe un comando, hereda descriptores de archivo y el entorno, y puede convertirse en el proceso que se comunica con un host remoto. Si un atacante puede sustituir ese ejecutable, todas las aprobaciones concedidas por encima de él pierden su valor.

Un diseño seguro tiene dos trabajos distintos. Las comprobaciones previas detectan un bundle dañado o mal instalado antes de que una petición llegue al ejecutor. Un requisito de código aplicado al lanzar pide a macOS que rechace la imagen real del proceso si su identidad de firma no coincide. Confundir ambos trabajos crea el conocido hueco en el que una aplicación verifica un archivo y ejecuta otro.

Deriva el helper del bundle que se está ejecutando

Obtén el helper del bundle de la aplicación que se ejecuta de verdad, nunca de PATH, del directorio actual, de una preferencia ni de una ruta enviada por el agente. La ubicación esperada debe ser una constante de compilación. Una pasarela que busca sp-ssh ya ha permitido que su entorno elija código ejecutable.

Apple documenta ubicaciones estándar para código anidado en un bundle, entre ellas Contents/MacOS y Contents/Helpers. Elige una, coloca allí únicamente código y haz que la compilación falle si el helper termina en otro sitio. Firma primero el helper y la aplicación exterior al final. Ese orden permite que la firma exterior selle la referencia al código anidado.

Foundation puede localizar un ejecutable auxiliar, pero la decisión de seguridad aún debe comprobar su relación exacta con el bundle principal. Resuelve los enlaces simbólicos, estandariza ambas URL y compara componentes de ruta, no prefijos de texto. Una prueba como candidate.path.hasPrefix(bundle.path) acepta rutas vecinas como /Applications/Good.app.backup y puede fallar con mayúsculas, minúsculas o normalización. El candidato debe ser exactamente la única URL esperada dentro del bundle en ejecución.

No aceptes como alternativa un helper copiado junto a la aplicación. Esa salida resulta tentadora durante el desarrollo porque disimula un empaquetado roto. En producción cambia en silencio el límite de confianza: deja de ser el contenido del bundle firmado y pasa a ser cualquier cosa que ocupe una ruta cercana. Las compilaciones de desarrollo deben usar una configuración explícita y fallar con claridad cuando el empaquetado sea incorrecto.

La ubicación demuestra algo sobre el empaquetado, no sobre la identidad. Quien pueda sustituir un archivo dentro de un bundle escribible puede conservar la misma ruta. Por eso las siguientes comprobaciones inspeccionan el objeto abierto y su firma.

Abre primero y después inspecciona la identidad

Abre el candidato con O_NOFOLLOW, conserva el descriptor abierto y llama a fstat sobre él. El orden importa. Llamar a lstat, revisar el resultado y ejecutar open después permite que otro proceso sustituya la entrada del directorio entre ambas operaciones.

La Secure Coding Guide de Apple recomienda operaciones basadas en descriptores por este motivo. En concreto, indica que hay que comprobar tipo, UID, GID, modo y número de enlaces después de abrir. Para un helper ejecutable, utilizo una comprobación previa con esta forma:

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

int inspect_helper(const char *path, uid_t expected_uid, struct stat *snapshot) {
    int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
    if (fd < 0) return -1;

    struct stat st;
    if (fstat(fd, &st) != 0 ||
        !S_ISREG(st.st_mode) ||
        st.st_uid != expected_uid ||
        (st.st_mode & (S_IWGRP | S_IWOTH)) != 0 ||
        (st.st_mode & S_IXUSR) == 0 ||
        st.st_nlink != 1) {
        int saved = errno ? errno : EPERM;
        close(fd);
        errno = saved;
        return -1;
    }

    *snapshot = st;
    return fd;
}

expected_uid procede del modelo de instalación, no del propio archivo. Una instalación administrada por el sistema puede exigir que pertenezca a root. Una aplicación instalada por usuario puede pertenecer legítimamente a ese usuario. No escribas una comprobación que exija UID 0 solo porque root parezca fiable. Apple también advierte que una ruta puede cruzar a otro sistema de archivos montado, donde el propietario aislado dice menos de lo que muchos desarrolladores creen.

La regla sobre el número de enlaces merece una decisión consciente. Un ejecutable normal dentro de un bundle suele tener un solo enlace físico, así que rechazar otra cantidad es razonable. Si el sistema de empaquetado crea enlaces físicos a propósito, documenta el hecho y prueba la cifra esperada. Eliminar la comprobación porque una compilación te sorprendió deja otro nombre desde el que se puede modificar el mismo inode.

Conserva el descriptor y la instantánea de stat hasta que termine el lanzamiento. No convierten en atómico un lanzamiento basado en ruta, pero permiten detectar sustituciones, registrar el dispositivo y el inode implicados y explicar un rechazo sin volver a abrir un nombre controlado por el atacante.

Comprueba cada directorio escribible de la cadena

Un modo perfecto para el helper no lo protege si su directorio padre se puede renombrar o modificar. El atacante no necesita permiso para editar los bytes del ejecutable cuando puede sustituir la entrada de directorio que apunta a ellos.

Recorre desde el padre del helper hasta la raíz del bundle mediante descriptores de directorio. En cada componente, rechaza enlaces simbólicos, confirma que sea un directorio, registra dispositivo e inode y aplica la regla de propiedad y escritura del modelo de instalación. openat y fstatat con un descriptor del padre son mejores que resolver repetidamente cadenas absolutas. El recorrido debe terminar en la raíz del bundle ya verificada, no en una ruta que solo tenga un sufijo conocido.

Aquí hay que hablar con franqueza de las instalaciones por usuario. Si el mismo usuario que ejecuta la pasarela posee un bundle escribible, otro proceso con esa cuenta puede sustituir sus archivos. Las comprobaciones de modo y propietario descubren una exposición accidental a otras cuentas, pero no protegen frente al control total de la cuenta actual. La identidad de firma sigue siendo importante porque un atacante no puede cumplir tu requisito designado con solo añadir una firma ad hoc.

Compara también los identificadores de dispositivo si la política exige un bundle local en un solo volumen. Apple señala que un nombre de ruta puede atravesar un punto de montaje. Rechazar límites inesperados es útil, pero no demuestra que el volumen sea fiable. Solo demuestra que el objeto no está donde el contrato de empaquetado decía que estaría.

Estas comprobaciones encuentran fallos de despliegue muy corrientes: un actualizador que deja un directorio temporal escribible por el grupo, un helper restaurado fuera de la aplicación firmada o un enlace simbólico creado por un script de desarrollo. Deben detener el ejecutor antes de que reciba un comando. Continuar con una advertencia convierte un error de empaquetado en lógica para elegir ejecutables.

Verifica la identidad, no solo una firma válida

Una firma válida responde si el código sigue siendo coherente con esa firma. No dice que lo haya firmado tu equipo ni que el ejecutable sea tu helper. En macOS puede haber código con firma ad hoc, y otro desarrollador puede producir código con una firma perfectamente válida.

Usa Code Signing Services con un requisito explícito que nombre el identificador de firma y la identidad de equipo esperados para el canal de distribución. Apple TN3127 aclara la distinción: un identificador de firma es un nombre elegido por quien firma, una identidad de firma contiene certificado y clave privada, y un requisito designado expresa qué cuenta como el mismo código entre versiones. Comparar el texto Authority mostrado es un atajo de diagnóstico, no un límite de seguridad para el producto.

Crea un SecStaticCode para la URL absoluta del helper, compila o carga el requisito y llama a SecStaticCodeCheckValidityWithErrors. Incluye kSecCSStrictValidate y kSecCSCheckAllArchitectures. Apple documenta que el comportamiento predeterminado puede validar solo la arquitectura nativa de un binario universal. Revisarlas todas impide que una arquitectura no probada lleve una firma distinta o dañada.

No uses kSecCSBasicValidateOnly para este trabajo. Esa opción omite la validación del ejecutable principal y de los recursos, lo que anula una comprobación de integridad. Tampoco confíes en el requisito designado del propio helper sin compararlo con un requisito controlado por la pasarela. Describirse a uno mismo no equivale a estar autorizado.

La aplicación exterior también necesita validación. Apple establece ubicaciones estándar para herramientas auxiliares con el fin de que el sistema de firma pueda tratarlas como código anidado. Una versión bien firmada tiene una cadena de intención: el helper satisface su requisito esperado y el sello de la aplicación registra el componente anidado. Comprobar ambos detecta un helper válido por sí mismo que se trasplantó a un bundle dañado.

Conserva internamente el CFError detallado y tradúcelo para el usuario a pocas categorías de rechazo: firma ausente, requisito que no coincide, recurso inválido, arquitectura no admitida o archivo modificado. Nunca conviertas un error de validación en un nuevo intento por otra ruta. Un fallo de firma significa que el ejecutor no está disponible.

Una comprobación estática no cierra la carrera

Aprueba primero el proceso agente
Sallyport identifica la autoridad de firma del nuevo agente antes de aprobar su sesión.

La validación estática depende de que el archivo no cambie. La documentación de Apple para SecStaticCodeCheckValidity lo dice expresamente y menciona sistemas de archivos dinámicos como los de red, union y FUSE. La advertencia también afecta a una entrada de directorio normal que pueda sustituirse: tras volver la validación, otro proceso puede renombrar otro ejecutable sobre la ruta antes de que posix_spawn la resuelva.

El fallo ocurre en cuatro movimientos:

  1. La pasarela resuelve /Applications/Example.app/Contents/Helpers/runner y valida el archivo A.
  2. Un proceso competidor renombra el archivo B sobre esa ruta.
  3. La pasarela solicita a Process o posix_spawn que ejecute la ruta.
  4. El kernel abre el archivo B, porque la llamada recibió un nombre y no el descriptor conservado para A.

Comparar stat antes y después de validar reduce la ventana y detecta muchos intentos, pero no vuelve indivisibles los pasos dos y tres. Calcular un hash propio tiene la misma limitación y repite trabajo ya expresado por la firma. Un archivo de bloqueo solo coordina procesos que aceptan respetarlo. Un atacante no lo hará.

Una recomendación habitual consiste en validar una vez al arrancar la aplicación y guardar el éxito. Es popular porque comprobar firmas cuesta y el código incluido parece inmutable durante un uso normal. Para una pasarela es un error. Las actualizaciones, restauraciones, cambios de volumen y sustituciones deliberadas pueden ocurrir mientras una aplicación de barra de menús sigue abierta. Guarda el objeto de requisito compilado si hace falta, como sugiere Apple, pero evalúa el ejecutable en cada lanzamiento.

Mantener un descriptor abierto sigue ayudando. Consérvalo durante la validación, toma otro fstat justo antes de lanzar y rechaza cualquier cambio de dispositivo, inode, tamaño, hora de modificación o cambio de metadatos. Toma una tercera instantánea tras crear el proceso para telemetría. Esto mejora el diagnóstico y eleva el esfuerzo de ataque, pero la afirmación debe ser precisa: solo un requisito aplicado por el sistema operativo al lanzar vincula identidad y creación del proceso.

Vincula el requisito a la creación del proceso

En sistemas con LightweightCodeRequirements, configura Process.launchRequirement antes de llamar a run. Apple indica que, si el ejecutable no satisface el LaunchCodeRequirement, el sistema operativo no ejecuta el proceso y genera un informe de fallo. La comprobación decisiva pasa a formar parte de la creación, cuando la selección de la ruta ya no puede separarse de la decisión.

La configuración principal en Swift es breve:

import Foundation
import LightweightCodeRequirements

func configuredProcess(helper: URL, team: String, identifier: String) throws -> Process {
    let requirement = try LaunchCodeRequirement.allOf {
        ValidationCategory(.developerID)
        TeamIdentifier(team)
        SigningIdentifier(identifier)
    }

    let process = Process()
    process.executableURL = helper
    process.launchRequirement = requirement
    return process
}

El equipo y el identificador deben proceder de la configuración de publicación compilada en la pasarela firmada. No los cargues desde preferencias junto al helper. Si distribuyes por varios canales de firma, crea y prueba un requisito explícito para cada canal admitido en lugar de debilitar una expresión hasta que todas las compilaciones pasen.

Aplica el requisito a un helper Mach-O, no a un script contenedor de shell. Apple señala que, para un script con shebang, el requisito se aplica al intérprete. Demostrar que /bin/bash es código de Apple no dice nada sobre los bytes del script que pretendes confiar. Coloca la lógica en código ejecutable firmado o trata el script como datos sellados que consume código fiable sin lanzarlo como ejecutor privilegiado.

Activa esta API según la disponibilidad del SDK y conserva la comprobación estática para diagnóstico. En destinos antiguos, POSIX_SPAWN_START_SUSPENDED puede crear el hijo suspendido antes de ejecutar código de espacio de usuario. Puedes obtener un SecCode dinámico por su PID, validarlo y después reanudarlo o matarlo. Es una alternativa delicada: comprueba todos los resultados, evita confundir PID, cierra descriptores no previstos y no envíes comandos ni credenciales antes del éxito. Si el modelo de amenaza no tolera esa complejidad, exige una versión del sistema que admita requisitos de lanzamiento.

Lanza con un contrato de proceso limpio y estrecho

Saca credenciales de la memoria
Los agentes envían acciones por Sallyport sin recibir claves en claro ni marcadores.

Verificar el binario no sanea lo que le entregas. Construye el vector de argumentos con campos estructurados, establece un entorno explícito, elige de forma deliberada el directorio de trabajo y pasa solo los descriptores necesarios. Nunca invoques un shell para componer un comando SSH.

Empieza con un entorno vacío o una lista permitida. Las variables que afectan a carga dinámica, descubrimiento de configuración, idioma, proxies o búsqueda del directorio personal pueden cambiar el comportamiento de un programa firmado sin modificar su código. Hardened Runtime y Library Validation reducen algunos ataques de carga, pero no convierten un entorno heredado arbitrario en una interfaz segura.

Trata entrada y salida estándar como un protocolo. Define tamaños máximos, rechaza campos sobrantes, fija un plazo y distingue un error del protocolo de un estado de salida SSH. Un helper sin estado no debe leer claves de variables, argumentos, archivos temporales ni rutas del agente. Debe recibir la petición mínima por un canal controlado y devolver el resultado mínimo.

Cierra todos los descriptores ajenos. O_CLOEXEC ayuda con los archivos de la comprobación, pero revisa el resto de la aplicación. Un hijo que hereda el descriptor de la base de datos de la bóveda, un listener IPC o un archivo de registro consigue acceso que ninguna comprobación pretendía conceder. Establece límites de recursos cuando el contrato lo permita y termina todo el grupo de procesos al cancelar o vencer el plazo.

Registra las entradas de la decisión sin secretos: ubicación relativa dentro del bundle, versión del requisito, instantáneas de dispositivo e inode, resultado, PID del hijo y motivo de terminación. El registro debe indicar qué lanzamiento se autorizó. Guardar solo la ruta textual pierde el hecho de que un mismo nombre puede haber señalado varios archivos durante un incidente.

Coloca la verificación dentro de la autorización

La aceptación del helper pertenece a la misma transacción que autoriza la acción y debe terminar antes de que la pasarela libere una capacidad al hijo. Comprobar al abrir la aplicación es demasiado pronto. Comprobar después de que una persona apruebe el comando es demasiado tarde si el fallo puede filtrar el comando, abrir un socket o activar una alternativa.

Modela el lanzamiento como una máquina de estados con límites explícitos e irreversibles. La pasarela recibe y analiza una acción cuando la petición aún no tiene acceso a credenciales. Confirma que la bóveda está disponible, resuelve y revisa el helper, obtiene la autorización humana y lanza con el requisito adjunto. Solo después del éxito crea el canal mínimo de credencial o conexión. Un fallo anterior destruye la acción pendiente.

La posición de la aprobación depende de su significado. Si la tarjeta pregunta si un agente puede ejecutar un comando SSH concreto, puede aparecer antes del lanzamiento costoso. El comando aprobado debe quedar inmutable y un fallo de validación debe consumir o cancelar esa aprobación, nunca reservarla para otro binario. Si la aprobación declara confianza en el proceso ejecutor, muéstrala solo después de conocer la identidad candidata. Ambos diseños pueden funcionar, pero el registro debe unir resumen de petición, decisión, requisito de firma y proceso hijo en un intento.

No expongas un secreto solo porque crear el proceso devolvió éxito. Prepara pipes o sockets antes si la API lo exige, pero mantén cerrado o bloqueado el extremo con credenciales. Espera al éxito del control de lanzamiento, registra la identidad y entonces envía la petición. Si el hijo necesita una operación con clave SSH, prefiere una interfaz limitada de firma o conexión a copiar bytes privados en su memoria. Cuanta menos autoridad reciba, menor será el coste de un error previo.

La cancelación requiere el mismo cuidado. El usuario puede revocar una sesión durante la validación o el agente puede desconectarse entre aprobación y lanzamiento. Revisa la generación de autorización justo antes de lanzar y de nuevo antes de liberar la petición. Si cambia, termina el hijo y cierra los canales. No es una carrera del sistema de archivos, pero sí otro intervalo entre comprobación y uso de la misma decisión.

Los lanzamientos concurrentes no deben compartir configuración mutable. Cada intento necesita su URL, requisito, argumentos, entorno, descriptores, plazo e identificador de auditoría inmutables. Una plantilla global de Process modificada por un hilo mientras otro llama a run puede enviar una petición al ejecutable o entorno equivocado aunque ambos binarios sean válidos. Sincroniza la transición breve que consume la aprobación e inicia el proceso, no toda la vida de cada conexión.

Un buen invariante es que ningún byte controlado por el hijo entre en el estado privilegiado antes de imponer la identidad. El helper puede escribir en un pipe nada más ejecutarse; mantén su parser desconectado hasta aceptarlo, limita la exposición del búfer y trata la salida temprana como infracción. Tampoco uses texto de error de un hijo no verificado para construir rutas privilegiadas, elegir credenciales o decidir otro binario.

Este orden produce un diario más claro. Un intento puede registrar request_received, candidate_preflight_passed, authorization_granted, launch_requirement_passed, request_released y el resultado final. Las transiciones ausentes quedan visibles. Un registro que salta de aprobación al código de salida no permite saber si el ejecutor esperado recibió el comando.

Verifica el hijo que realmente arrancó

Verifica el rastro sin conexión
La cadena de auditoría se verifica sobre el texto cifrado sin abrir la bóveda.

El control al lanzar debe ser la barrera decisiva, pero la observación posterior detecta errores de integración y aporta una identidad estable. Cuando run tenga éxito, recoge el PID y obtén un SecCode dinámico del proceso. Compruébalo con el requisito correspondiente antes de conectar el parser o liberar datos sensibles.

La diferencia de Apple entre SecStaticCode y SecCode ayuda aquí. Un objeto estático describe código en disco y no está ligado de forma inherente al código en ejecución. Uno dinámico representa el código cargado en el proceso. Responden preguntas distintas: la comprobación estática explica si el candidato instalado está íntegro; la dinámica confirma la identidad que macOS asignó al hijo actual.

Buscar solo por PID tiene riesgos. Los PID se reutilizan y un hijo breve puede salir entre run, búsqueda y validación. No poder obtener o validar el objeto debe contar como fallo. Si una API IPC proporciona un token de auditoría, usa esa referencia más fuerte y no un PID indicado en un mensaje. Nunca confíes en que el hijo informe de su PID, identificador o ruta.

La comprobación posterior no debe ser la única en una plataforma donde el hijo obtiene CPU antes. Un sustituto puede actuar entre el spawn y la inspección. Iniciar suspendido antes del código de usuario reduce el hueco en sistemas antiguos, pero la implementación solo debe reanudar el PID validado y matarlo en cualquier error. El requisito de lanzamiento es más sencillo porque el sistema rechaza la discrepancia antes de ejecutar.

Tras aceptar, conserva un identificador del proceso y vincula cada mensaje a esa instancia. No vuelvas a abrir un socket con nombre ni reconectes a un endpoint que otro proceso pueda ocupar. Al salir el hijo, cierra canales, invalida la acción y exige un lanzamiento nuevo. El primer hijo válido no autoriza otro que reutilice su PID o nombre.

Registra evidencia instalada y en ejecución. Para el objeto instalado, ruta relativa, dispositivo, inode, tamaño, marcas de tiempo y resultado estático. Para el proceso, PID, revisión del requisito, resultado impuesto al lanzar, validación dinámica, hora de inicio y estado final. No son secretos y permiten distinguir versión dañada, colisión de actualización, error de configuración e intento de sustitución sin volcar comandos o credenciales.

La identidad necesita una regla de vida. Si el helper ejecuta otro programa, la identidad verificada no se transfiere a la nueva imagen. Evita un wrapper firmado que valida y después llama a un ssh arbitrario mediante PATH. Si hay una transición exec, el ejecutable final necesita ruta fija y requisito propio, o el helper fiable debe implementar el protocolo. La firma del lanzador no dice qué programa elegirá después.

Vigila también bibliotecas y configuración. El ejecutable principal puede satisfacer el requisito mientras variables inseguras o búsquedas escribibles cambian su conducta. Firma dependencias, activa opciones adecuadas de Hardened Runtime, usa Library Validation cuando sea compatible y elimina rutas de búsqueda del entorno. La configuración debe ser entrada validada por la pasarela, no archivos descubiertos por el hijo en el directorio personal.

Por último, prueba lo que afirma la telemetría. En una prueba de sustitución, la instantánea debe identificar A, el control debe lanzar A o rechazar B y la comprobación dinámica debe coincidir. Si el registro dice que A pasó mientras B se ejecutó sin quedar registrado, el modelo sigue al nombre, no al proceso.

Falla de forma cerrada sin impedir actualizaciones

Cualquier fallo debe detener ese ejecutor y dejar el resto de la aplicación en un estado comprensible. No recurras al ssh del sistema, no busques otro directorio, no retires el requisito ni pidas al usuario aprobar un helper sin identidad. Una aprobación no repara la identidad del ejecutable.

Las actualizaciones requieren una transición separada. Deja de aceptar trabajo SSH, permite terminar a los hijos activos o termínalos, instala el bundle firmado completo mediante sustitución atómica y repite todas las comprobaciones. Un helper y una aplicación de versiones distintas pueden tener firmas válidas y romper el contrato de protocolo. Añade un saludo de versión tras lanzar y rechaza discrepancias antes de enviar una acción.

Decide qué cambios exigen recuperación visible. Un helper ausente tras una actualización parcial pide reinstalar. Un equipo o identificador distinto puede indicar manipulación y merece un mensaje más fuerte. Un modo diferente puede proceder de una copia de seguridad. Conserva el error exacto internamente y ofrece una acción breve que no enseñe a saltarse el control.

Prueba la sustitución, no solo firmas correctas. La suite debe cubrir un enlace simbólico en la ruta, un segundo enlace físico, padres escribibles por grupo, un sustituto con firma ad hoc, un binario bien firmado con identificador incorrecto, una slice no nativa dañada, sustitución entre preflight y lanzamiento y actualización con la aplicación abierta. Un hook que pause después de validar hace reproducible la carrera.

Sallyport usa un helper Go incluido y sin estado para su canal SSH, mientras el núcleo de la bóveda permanece en la aplicación firmada de la barra de menús. Esa división mantiene los secretos fuera del helper, pero no vuelve opcional su verificación: la pasarela aún debe demostrar qué ejecutor arranca antes de entregarle una acción aprobada.

La regla de aceptación debe caber en una línea de revisión: el archivo esperado del bundle en ejecución supera las comprobaciones de descriptor y directorios, satisface el requisito de firma de publicación y la creación del proceso impone esa misma identidad. Si la plataforma no puede imponer lo último, documenta la garantía menor y reduce lo que el hijo puede recibir.

FAQ

¿Por qué no basta con comprobar la ruta del helper SSH?

Una ruta es un nombre, no una identidad estable. Otro proceso puede sustituir su entrada después de la comprobación, por lo que la pasarela debe inspeccionar el archivo abierto e imponer un requisito al lanzar.

¿Debe una aplicación macOS buscar su helper mediante PATH?

No. PATH deja que el entorno elija código y hace que un empaquetado roto parezca una búsqueda correcta. Deriva una URL exacta del bundle en ejecución y rechaza cualquier alternativa.

¿Qué metadatos debe comprobar la pasarela?

Tras abrir con O_NOFOLLOW, usa fstat para confirmar que sea un archivo ejecutable normal con propietario, permisos y número de enlaces esperados. Conserva dispositivo e inode para comparar y registrar.

¿Una firma válida demuestra que el helper es mío?

No. Solo demuestra coherencia con una firma, y macOS puede ejecutar código de otros desarrolladores o con firma ad hoc. Aplica un requisito para identificador, equipo y categoría de distribución.

¿Por qué validar todas las arquitecturas de un helper universal?

Apple indica que la validación estática suele comprobar solo la arquitectura nativa. kSecCSCheckAllArchitectures inspecciona cada slice, incluida una que pueda ejecutar otra máquina o una selección explícita.

¿Un descriptor abierto elimina la carrera de sustitución?

Estabiliza el archivo inspeccionado, pero Process y posix_spawn siguen seleccionando por ruta. Úsalo para instantáneas y diagnóstico, y aplica un requisito de lanzamiento para vincular identidad y proceso.

¿Qué aporta Process.launchRequirement?

Pide al sistema operativo evaluar un LaunchCodeRequirement durante el lanzamiento. Si falla, el proceso no se ejecuta, lo que cierra el hueco de una comprobación estática.

¿Siguen siendo útiles propietario y permisos con firma de código?

Sí, porque detectan instalaciones inseguras y reducen oportunidades de sustitución. Son evidencia auxiliar y no reemplazan identidad de firma ni control al lanzar.

¿Debe la aplicación guardar una verificación correcta?

Puede guardar el requisito compilado si las mediciones lo justifican, pero debe volver a validar en cada lanzamiento. Una aplicación abierta puede atravesar actualizaciones, restauraciones o sustituciones.

¿Cómo debe reaccionar la pasarela si falla la verificación?

Debe rechazar ese ejecutor, registrar el motivo exacto y ofrecer una recuperación adecuada, como reinstalar una aplicación dañada. Nunca debe buscar otro binario SSH ni rebajar el requisito.

Sallyport

Sallyport ejecuta llamadas de API y comandos SSH por tu agente de IA. Las claves se quedan en una bóveda local de tu Mac; tú apruebas cada ejecución y cada acción queda en un registro sellado.

© 2026 Sallyport · Código abierto bajo Apache-2.0 · Oleg Sotnikov