Seguridad del cierre de sesión en macOS: termina la autoridad del agente
La seguridad del cierre de sesión en macOS debe revocar el acceso a la bóveda, las aprobaciones del agente y las acciones en cola antes de que un desarrollador ausente pueda iniciar otra llamada.

El cierre de sesión de un desarrollador debe terminar la autoridad del agente local, incluso si un proceso del agente, una solicitud de red o una aplicación de la barra de menús todavía no se han enterado. Tratar el cierre de sesión como una petición amable de limpieza crea una ventana en la que un equipo sin vigilancia aún puede actuar con las credenciales de una persona.
Esto importa especialmente en los agentes de programación, porque su trabajo útil cruza un límite. Llaman a API, abren sesiones SSH, crean tickets, publican paquetes o cambian infraestructura. Si obtuvieron su autoridad de la persona que estaba frente al teclado, esta caduca cuando esa persona abandona la sesión de macOS. El sistema debe conservar las pruebas de lo ocurrido, pero no puede conservar la capacidad de hacer algo más.
El error que veo repetirse consiste en mezclar tres cosas distintas: un secreto almacenado localmente, una aprobación concedida a un proceso y una acción ya iniciada. Cada una necesita un comportamiento de apagado diferente. Un único manejador general de quit llega demasiado tarde y resulta demasiado impreciso.
El cierre de sesión es un límite de autoridad, no un evento de la aplicación
La seguridad del cierre de sesión en macOS exige que la ausencia de una sesión de usuario interactiva deniegue futuras acciones con credenciales, independientemente de que todas las aplicaciones se cierren correctamente. Una aplicación de escritorio puede recibir notificaciones de terminación durante un cierre de sesión normal, pero la seguridad no puede depender de que esas notificaciones lleguen, terminen o se ejecuten en un orden conveniente.
Un usuario puede cerrar un portátil, cambiar de usuario, forzar el cierre de una aplicación, perder la alimentación o iniciar el cierre de sesión mientras un agente espera a un endpoint lento. El sistema operativo también puede terminar los procesos en un orden que tu código no esperaba. Un diseño que dice «revocaremos el acceso en applicationWillTerminate» ya ha aceptado demasiada incertidumbre.
La documentación de launchd de Apple establece aquí la distinción importante. Separa los dominios del sistema, del usuario y del inicio de sesión gráfico. Un trabajo dentro de un dominio gráfico de usuario pertenece a una sesión concreta iniciada, mientras que un trabajo del sistema tiene otro ciclo de vida y otro modelo de autoridad. No interpretes «el proceso sigue ejecutándose» como una prueba de que todavía tiene permiso para actuar en nombre del usuario que se ha marchado.
Construye la comprobación de autorización alrededor de un dato actual de la sesión que la puerta de enlace de acciones pueda verificar en el momento de la llamada. Cada acción debe preguntar, en este orden:
- ¿La bóveda está abierta actualmente para esta sesión de usuario iniciada?
- ¿Esta solicitud pertenece a un proceso de agente vivo y autorizado dentro de esa misma sesión?
- ¿Esta credencial exige una aprobación para este uso concreto?
- ¿Un evento de cierre de sesión o revocación de sesión ya ha avanzado la generación de la sesión?
La cuarta comprobación evita una carrera sutil. Una solicitud puede superar las tres primeras comprobaciones, quedarse en una cola y llegar al ejecutor después de que empiece el cierre de sesión. Un número de generación, comprobado de nuevo justo antes de inyectar la credencial o ejecutar SSH, hace que esa solicitud obsoleta falle.
No hagas que el comportamiento de cierre de sesión dependa de que un agente acepte detenerse. En el límite, el agente es un solicitante no confiable. Puede estar confundido, ocupado, comprometido o simplemente haber desaparecido.
Una bóveda bloqueada debe denegar antes de comenzar la limpieza
La puerta de la bóveda debe cerrarse primero y de forma síncrona desde el punto de vista del ejecutor. Una vez cerrada, no puede comenzar ninguna nueva inyección de credenciales HTTP ni ninguna nueva autenticación SSH. La limpieza puede venir después, pero no puede ser el mecanismo que haga seguro el sistema.
El orden parece obvio hasta que una aplicación mantiene una cola de solicitudes. Un fallo típico es el siguiente: un agente envía cinco llamadas de despliegue, la interfaz inicia la limpieza del cierre de sesión, la aplicación borra la tarjeta de sesión visible y un hilo de trabajo extrae la cuarta llamada con una referencia de credencial que había resuelto antes. La aplicación parece haber cerrado sesión, pero la acción aún llega al servicio externo.
Mantén un único objeto de autoridad en el proceso que controla el uso de secretos. Debe contener un identificador de sesión opaco, un contador de generación y un estado habilitado. Los trabajadores nunca reciben los bytes de los secretos. Reciben una solicitud de acción y deben obtener un permiso de autorización nuevo justo antes de que la bóveda ejecute la acción.
Una estructura pequeña es suficiente:
AuthorityState {
sessionID: 6C17...
generation: 41
vaultOpen: true
logoutStarted: false
}
execute(request):
lease = authority.issueLease(request, generation: 41)
vault.perform(request, lease)
beginLogout():
authority.logoutStarted = true
authority.vaultOpen = false
authority.generation = 42
cancelPendingRequests()
vault.perform debe rechazar el permiso si su generación ya no coincide. Esa segunda comparación debe estar lo más cerca posible del punto en que la bóveda proporciona un encabezado HTTP, inicia un helper SSH o firma una solicitud. Comprobarlo solo cuando la solicitud entra en la cola deja una carrera suficientemente grande como para importar.
En una aplicación de macOS que protege su bóveda con Secure Enclave y Touch ID, el acceso protegido por hardware resulta útil porque proporciona a la puerta un propietario local claro. No elimina la necesidad del estado de sesión. Una solicitud biométrica aceptada antes del cierre de sesión no puede autorizar una llamada después del cierre de sesión.
La puerta de bóveda de Sallyport sigue esta regla: mientras está bloqueada, se deniega toda acción. Ese límite fijo es preferible a una lista de excepciones de apagado, porque las listas crecen hasta que nadie puede explicar qué llamada sigue escapándose.
Una aprobación activa pertenece a un proceso y una sesión
Una aprobación del usuario debe vincularse a un proceso de agente concreto, a su autoridad de firma de código y a la sesión de inicio de macOS actual. Nunca debe significar «esta cuenta aprobó esta herramienta en algún momento anterior».
La identidad de un proceso es más que su identificador. Los identificadores de proceso se reutilizan. Un llamador que solo registra el PID 4812 puede aprobar por accidente un proceso no relacionado después de suficiente rotación. Registra junto al PID la hora de inicio del proceso y su identidad de firma de código. Si el proceso es hijo de un terminal o de una integración de editor, conserva suficiente información del proceso padre para explicar la ruta en el registro de aprobación, pero no conviertas la ascendencia en tu única señal de confianza. Los wrappers de shell y los supervisores de procesos la cambian constantemente.
La tarjeta de aprobación debe comenzar con la autoridad de firma, porque así ofrece al desarrollador una pregunta significativa: «¿Quiero que este proceso de agente firmado actúe en esta sesión?». El nombre de un paquete o una cadena arbitraria suministrada mediante MCP no responde a esa pregunta.
Cuando comienza el cierre de sesión, descarta todas las aprobaciones activas de esa sesión. No las suspendas. No las serialices para el siguiente inicio de sesión. No las recrees porque el mismo binario vuelva después de reiniciarse. El desarrollador debe aprobar la nueva ejecución como una ejecución nueva.
Esto también se aplica a los diálogos de aprobación que ya están en pantalla. Deben desaparecer o quedar inertes cuando cambie la sesión. Un clic retrasado sobre una tarjeta antigua no debe reactivar una autorización que ya ha muerto. Da a cada aviso de aprobación una caducidad vinculada a la misma generación de sesión que protege las solicitudes.
Hay una distinción útil que muchas implementaciones difuminan:
- Desbloquear la bóveda permite que la puerta de enlace local considere acciones.
- La autorización de sesión permite que un proceso de agente identificado envíe acciones.
- La aprobación por uso permite un uso concreto de una credencial.
El cierre de sesión invalida las tres, pero no contienen las mismas pruebas ni tienen el mismo momento. La bóveda se cierra de inmediato. Las aprobaciones de sesión dejan de ser válidas como grupo. Los avisos por uso fallan individualmente porque cambió la generación de sesión. Combinar todo en un único booleano dificulta auditar por qué una llamada tuvo éxito o falló.
Las herramientas en ejecución necesitan cancelación e incertidumbre honesta
El cierre de sesión debe detener el trabajo que todavía no ha cruzado el límite externo e intentar detener el trabajo que ya lo ha cruzado. No puede deshacer una operación que un servicio remoto ya aceptó.
Separa una acción en estados con significado operativo:
queued -> authorized -> dispatched -> response received
\-> cancelled
Una acción en cola no ha salido del Mac. Elimínala de la cola e informa cancelled_before_dispatch. Una acción autorizada puede conservar solo un permiso interno de corta duración. Invalida el permiso antes del envío e informa revoked_before_dispatch si el trabajador llega demasiado tarde.
Una acción enviada es diferente. El sistema remoto puede haberla recibido aunque el proceso local nunca obtenga una respuesta. No la informes como cancelada simplemente porque cerraste un socket o mataste un helper. Registra logout_during_dispatch, captura el identificador de la solicitud si el protocolo remoto lo proporciona e indica al usuario que el resultado es desconocido hasta que inspeccione el sistema remoto.
Las solicitudes HTTP merecen especial cuidado. Cerrar una conexión de cliente puede detener una carga antes de que el servidor la lea, o puede ocurrir después de que el servidor haya confirmado un cambio. Los tokens de idempotencia reducen el daño cuando el usuario reintenta más tarde, pero no convierten una solicitud incierta en una solicitud cancelada. Para las operaciones que crean un recurso externo, envía un identificador de idempotencia que la API remota realmente respete y registra ese identificador sin guardar la credencial.
SSH es aún menos predecible. Enviar una señal a un helper local puede matar el proceso local mientras el comando remoto sigue ejecutándose en su propio grupo de procesos. Cuando controles el entorno remoto, ejecuta el trabajo largo bajo un supervisor de trabajos remoto con un identificador explícito y una ruta de cancelación. Cuando no lo controles, dilo en el registro de actividad. Fingir que cancelaste una migración remota porque se cerró el terminal local es la forma de empeorar un incidente.
No hagas que el cierre de sesión espere indefinidamente a la limpieza. Cierra primero la autoridad, pide a los trabajadores que cancelen, concede a la aplicación un intervalo breve y limitado para limpiar y deja que el sistema operativo termine el cierre de sesión. La propiedad de seguridad es denegar usos futuros. Una salida ordenada es una comodidad de mejor esfuerzo.
La persistencia en segundo plano cambia el modelo de amenazas
Una aplicación por usuario que sigue actuando después del cierre de sesión ha pasado de ser un asistente de escritorio a un servicio desatendido. Puede ser apropiado para una cuenta de servicio diseñada deliberadamente. No es apropiado como efecto accidental de una aplicación de la barra de menús.
Evita instalar un helper con privilegios o un trabajo de lanzamiento en el dominio del sistema solo para mantener vivo un agente durante el cierre de sesión. Esta decisión es popular porque hace que los trabajos largos parezcan fiables. También separa la ruta de acción de la persona que la aprobó y suele ampliar el acceso más allá de la sesión de usuario original.
Si un equipo realmente necesita que el trabajo continúe después de que un desarrollador se marche, dale un entorno independiente. Usa una identidad de servicio explícita, credenciales con alcance remoto limitado y caducidad, una propiedad definida, un registro de auditoría y un procedimiento de cancelación que pueda usar otro operador. Haz visible la transferencia. Un agente local no debe asumir ese papel en silencio.
El cambio rápido de usuario expone el mismo problema. El usuario A puede dejar abierta una sesión gráfica mientras el usuario B inicia sesión. El usuario B no debe poder aprobar ni observar la autoridad del agente del usuario A. Vincula cada instancia de la puerta de enlace, cada registro de aprobación y cada comprobación de acceso a la bóveda con el usuario y la sesión gráfica correctos. Un daemon de máquina que mezcle casualmente a ambos usuarios necesita un aislamiento muy cuidadoso. La mayoría de las herramientas de escritorio deberían evitar esa arquitectura.
El reposo no es el cierre de sesión. Un Mac en reposo puede reanudar la misma sesión de usuario, así que los equipos necesitan una decisión independiente para el reposo y el bloqueo de pantalla. Para credenciales sensibles, suele ser sensato cerrar la bóveda al bloquear la pantalla. Para trabajos locales menos sensibles, la puerta de enlace puede conservar el estado de la bóveda, pero exigir una aprobación nueva después de reactivarse. Sea cual sea la política, no la describas como comportamiento de cierre de sesión. Los usuarios y quienes revisan incidentes necesitan palabras precisas.
Los registros deben sobrevivir a la autoridad sin convertirse en un segundo almacén de secretos
Necesitas un registro duradero de la autoridad que terminó, especialmente cuando una acción de red coincidió con el cierre de sesión. No necesitas una segunda base de datos llena de tokens, cuerpos de solicitudes o material privado de SSH.
Escribe los eventos del ciclo de vida como hechos: sesión abierta, proceso de agente aprobado, solicitud enviada, uso de credencial autorizado, envío iniciado, cierre de sesión observado, permiso revocado, terminación del helper solicitada y resultado final. Incluye identificadores estables que permitan a un operador relacionar los eventos, pero minimiza el contenido del usuario. Un registro de actividad puede indicar que una llamada HTTP a un endpoint configurado tuvo éxito sin conservar un encabezado de autorización ni el cuerpo de una respuesta sensible.
Un registro de auditoría encadenado mediante hashes aporta una propiedad que no tiene el registro normal de una aplicación: un verificador sin conexión puede detectar registros eliminados o alterados. Resulta útil tras un despliegue cuestionado o una posible intrusión local, pero no hace que un registro sea veraz por arte de magia. El registro demuestra la continuidad entre los registros que contiene. No puede demostrar que un proceso malicioso nunca dejó de registrar antes de actuar.
Por eso, escribe el evento de revocación antes de cualquier limpieza de procesos que se haga como mejor esfuerzo. Si la aplicación falla mientras mata un helper, el registro debe mostrar que la autoridad local terminó y que el resultado de la acción puede ser incierto. Un registro de auditoría que solo anota finalizaciones limpias enseña a los operadores la lección equivocada.
Sallyport proyecta sus diarios de sesión y actividad desde un único registro de auditoría cifrado, encadenado mediante hashes y ciego a la escritura, y sp audit verify puede comprobar esa cadena sin conexión sobre el texto cifrado. Así, un equipo puede comprobar la continuidad sin abrir la bóveda solo para verificar si el cierre de sesión revocó una ejecución.
La pregunta de auditoría útil es concreta: «¿Qué proceso tenía autoridad, qué ruta de credenciales solicitó y qué sabía la puerta de enlace cuando el usuario se marchó?». Un enorme registro de depuración rara vez responde.
Prueba las carreras en lugar de probar un cierre limpio
Una prueba de cierre de sesión debe hacer que una acción coincida con el límite. Las pruebas que llaman a un método de apagado de la aplicación después de que todos los trabajadores hayan terminado solo verifican que la limpieza normal funciona.
Empieza con un endpoint bajo tu control que acepte una solicitud, registre su recepción y retrase la respuesta. Envía una acción mediante la puerta de enlace del agente e inicia el cierre de sesión después de que la puerta de enlace haya marcado la acción como enviada, pero antes de que llegue la respuesta. En el siguiente inicio de sesión, compara el estado de auditoría local con el registro del endpoint. El resultado esperado no siempre es «cancelada». Es un estado preciso, como logout_during_dispatch, junto con un identificador de solicitud que puedas investigar.
Usa una prueba independiente para el trabajo en cola. Pausa a un trabajador después de que tome un elemento de la cola, pero antes de que solicite a la bóveda un permiso de acción. Inicia el cierre de sesión, libera al trabajador y comprueba que obtiene un resultado de revocación en lugar de enviar algo. Esta prueba detecta el error habitual de comprobar la autorización solo cuando las solicitudes entran en la cola.
Después, prueba los casos incómodos:
- Cambia a otro usuario mientras la sesión original sigue abierta.
- Bloquea la pantalla, reactiva el equipo y prueba la política que elegiste para esa transición.
- Fuerza el cierre del agente mientras una solicitud espera aprobación y luego inicia un proceso nuevo con un PID reutilizado, si tu entorno de pruebas puede organizarlo.
- Interrumpe la puerta de enlace durante el apagado e inspecciona si el registro de auditoría sigue identificando el trabajo no resuelto.
- Envía un comando SSH que inicie trabajo remoto y confirma el comportamiento del lado remoto en lugar de confiar en el estado de salida del helper local.
En macOS, inspecciona los dominios de lanzamiento durante la preparación de la prueba para saber qué has iniciado realmente:
uid="$(id -u)"
launchctl print "gui/$uid" | grep -E "(agent-gateway|your-test-label)"
La salida varía según el trabajo instalado y la versión de macOS, pero debería mostrar el trabajo correspondiente dentro del dominio gui/<uid> actual. Si el trabajo de prueba aparece en un dominio del sistema, el resultado del cierre de sesión dice muy poco sobre el comportamiento de sesión de una aplicación de escritorio normal.
No automatices primero un cierre de sesión real contra la cuenta principal de un desarrollador. Usa una cuenta local desechable, una credencial de API desechable y un endpoint donde puedas inspeccionar cada solicitud. Las pruebas de cierre de sesión pueden destruir trabajo no guardado y dejar el estado remoto a medio cambiar. No es una razón para omitirlas. Es una razón para dejar de tratarlas como una prueba unitaria.
Las credenciales remotas deben limitar el alcance de una llamada tardía
La revocación local no puede retroceder en el tiempo y revocar un token bearer que un servicio remoto ya aceptó. El diseño de las credenciales remotas limita el daño si la puerta de enlace descubre una solicitud demasiado tarde, una aplicación falla o un equipo queda comprometido antes del cierre de sesión.
Prefiere credenciales remotas con permisos limitados y una vida corta cuando el objetivo las admita. Usa credenciales separadas para entornos diferentes. Un agente que puede actualizar un despliegue de staging no debe recibir una credencial capaz de eliminar datos de producción solo porque ambos endpoints utilizan el mismo proveedor de API.
Para SSH, usa una cuenta remota dedicada y restringe lo que puede hacer. Si un comando debe iniciar una tarea de larga duración, haz visible en el lado remoto la propiedad y la cancelación de la tarea. Una identidad SSH personal con acceso amplio al shell resulta cómoda hasta que necesitas explicar un trabajo que continuó después de que desapareciera su proceso padre local.
No pases credenciales al agente como variables de entorno, texto de configuración, sustituciones de marcadores o argumentos del shell. Una vez que un secreto entra en el llamador, el cierre de sesión puede detener futuras acciones locales, pero no puede hacer que esa copia desaparezca de la memoria del proceso, el historial del shell, los informes de fallos o una transcripción. Una puerta de enlace debe inyectar la credencial solo en la acción que ejecuta y devolver el resultado al agente.
Este diseño da al cierre de sesión una tarea clara: cerrar la bóveda, invalidar las concesiones activas, rechazar los permisos obsoletos, detener el trabajo pendiente y registrar la incertidumbre del trabajo ya enviado. No puede prometer deshacer Internet. Sí puede impedir que la siguiente llamada tome prestada la autoridad de una persona que ya no está.
La condición de salida debe ser fácil de explicar
Escribe la regla en una frase que un desarrollador cansado pueda usar durante un incidente: cuando termina la sesión de usuario de macOS, ningún proceso de agente local puede iniciar otra acción con credenciales bajo la autoridad de ese usuario.
Todo lo demás se desprende de esa regla. La puerta de la bóveda se cierra antes de la limpieza. Las aprobaciones mueren con el proceso y la sesión que las obtuvieron. Una solicitud en cola pierde su permiso. Una solicitud enviada recibe un estado desconocido honesto hasta que un sistema remoto confirme el resultado. Los registros siguen disponibles para revisión, pero los secretos y la autoridad ejecutable no.
Si un producto necesita autoridad después del cierre de sesión, construye un servicio desatendido explícito con su propia identidad. No ocultes esa decisión en la ruta de apagado de una aplicación de escritorio.
FAQ
¿Bloquear la pantalla de un Mac equivale a cerrar sesión para la seguridad de los agentes?
No. El bloqueo de pantalla protege la consola frente a un uso casual, pero la sesión del usuario y sus procesos pueden seguir activos. Trata el bloqueo de pantalla como un motivo para restringir acciones o exigir una nueva aprobación. El cierre de sesión debe terminar la autoridad local e invalidar por completo la sesión.
¿Qué debe ocurrir si un agente realiza una solicitud durante el cierre de sesión?
Debe denegarse de inmediato. La puerta de la bóveda tiene que cerrarse antes de cualquier tarea de apagado retrasada, solicitud de limpieza o animación de la interfaz. Si una herramienta ya inició una operación externa, registra su estado conocido e impide nuevas llamadas después del cierre de sesión.
¿Debe un agente aprobado seguir aprobado cuando el usuario vuelve a iniciar sesión?
No. Una aprobación pertenece a un proceso de agente concreto dentro de una sesión de usuario específica, no a la cuenta en general. Un inicio de sesión nuevo necesita comprobar de nuevo la identidad del proceso y tomar una nueva decisión de autorización.
¿Los registros de auditoría deben sobrevivir al cierre de sesión de macOS?
Un sistema seguro debe conservar las pruebas, no la autoridad. Guarda un registro de auditoría cifrado con la sesión, las aprobaciones, las llamadas denegadas y cualquier revocación del cierre de sesión, pero no restaures credenciales ejecutables ni el estado de aprobación desde ese registro.
¿El cierre de sesión puede detener de forma segura un comando SSH o una solicitud HTTP que ya está en curso?
Intenta detenerlo, pero no finjas que la terminación demuestra que la acción externa se detuvo. Registra el identificador del proceso, su proceso padre, el comando y el estado de la acción antes de revocarla. Para el trabajo remoto, usa credenciales con permisos limitados y cancelación o caducidad en el lado remoto cuando el servicio lo permita.
¿Qué ocurre si un proceso de macOS por usuario sobrevive al cierre de sesión?
Debe fallar de forma segura. La aplicación tiene que tratar la ausencia de una sesión interactiva como un fallo de autoridad, aunque un proceso en segundo plano siga activo brevemente. Un inicio de sesión posterior puede iniciar una instancia de servicio nueva, pero no debe heredar los permisos de la anterior.
¿Debe continuar el trabajo autónomo de un agente después de que el desarrollador cierre sesión?
Solo si el usuario ha creado deliberadamente una autoridad de servicio independiente, con credenciales, responsables, registros de auditoría y reglas de apagado separados. Una puerta de enlace de agentes de escritorio que toma prestada la autoridad local de un desarrollador no debe convertirse silenciosamente en un servidor solo porque un comando tarda mucho.
¿Cómo puede un agente usar credenciales de API o SSH sin conservarlas después del cierre de sesión?
Sí, si la aplicación se ejecuta en el mismo Mac y es propietaria de las credenciales, puede revocar la autoridad al cerrar sesión sin colocar los secretos en el proceso del agente. Sallyport mantiene los secretos en su bóveda cifrada y realiza la acción HTTP o SSH directamente, por lo que el agente recibe un resultado en lugar de material de credenciales reutilizable.
¿Qué debe registrar una auditoría cuando termina la autoridad local de un agente?
Como mínimo, registra la hora, la identidad del usuario, el identificador de sesión, la identidad del proceso del agente, la autoridad aprobada, el identificador de la acción, el canal, el resultado y el motivo de la revocación. No incluyas cuerpos de solicitudes o respuestas, tokens ni material privado de SSH sin procesar en un registro de lectura general.
¿Es adecuado el cierre de sesión de macOS para trabajos de agentes de producción de larga duración?
No uses una sesión de usuario como lugar improvisado para ejecutar automatización de producción sin supervisión. Coloca el trabajo desatendido detrás de una cuenta de servicio, credenciales remotas de corta duración, una propiedad explícita del trabajo y un proceso de despliegue revisable. El cierre de sesión de un desarrollador no debe dejar autoridad de producción personal en un escritorio.