Multiplexación de conexiones SSH: riesgos de los sockets de control
La multiplexación de conexiones SSH puede mantener activo el acceso autenticado después de que termine una tarea. Aprende a aislar los sockets de control, cerrar las sesiones y conservar las evidencias.

La multiplexación de conexiones SSH puede dejar abierta una ruta autenticada hacia un host después de que termine el comando que la creó. Es un comportamiento deliberado. También es fácil interpretarlo mal cuando una tarea automatizada, una ejecución de un agente o un trabajo de despliegue afirma haber terminado.
He visto a equipos investigar una ventana de mantenimiento supuestamente cerrada y descubrir que un master local de ssh todavía mantenía un transporte activo, que un socket aún aceptaba nuevos clientes y que un comando posterior lo reutilizaba sin otra autenticación interactiva. No ocurrió nada extraño. La configuración hacía exactamente lo que documenta OpenSSH. El equipo había confundido el final de un comando de shell con el final del acceso.
La diferencia importante está entre un comando del cliente completado y un transporte autenticado terminado. La multiplexación convierte ambos en eventos separados. Si ejecutas trabajo autónomo mediante SSH, la limpieza y las evidencias deben tener en cuenta esa separación.
Un socket de control puede durar más que el comando que lo abrió
La multiplexación SSH usa una única conexión SSH de larga duración, llamada master, para transportar el trabajo de procesos cliente SSH posteriores. En la documentación de OpenSSH, esos clientes posteriores suelen llamarse slaves. Cuando un slave contacta correctamente con el master local a través de su socket de control, no repite la configuración normal de la conexión ni la autenticación del usuario.
Una configuración típica parece inofensiva:
Host build-box
HostName 192.0.2.44
User deploy
ControlMaster auto
ControlPath ~/.ssh/cm/%C
ControlPersist 20m
El primer ssh build-box crea una conexión de red y un socket de dominio Unix en ~/.ssh/cm/. Los comandos posteriores, como ssh build-box 'uname -a', scp y sftp, pueden usar ese socket. Con ControlPersist 20m, el master permanece disponible durante veinte minutos después de cerrar su última sesión.
Por eso esta secuencia es normal:
- Una tarea ejecuta
ssh build-box 'apply-change'y termina con el estado cero. - El master sigue conectado porque
ControlPersistle indica que permanezca disponible. - Otro proceso local ejecuta
ssh build-box 'read-status'diecinueve minutos después. - Ese proceso abre un canal a través del transporte ya autenticado.
El comando posterior puede estar autorizado por el sistema operativo local y por los permisos del socket, pero no hace que el host remoto vea una nueva autenticación SSH. Un revisor que solo mire el registro del inicio de sesión inicial puede suponer fácilmente que la actividad terminó mucho antes.
OpenSSH describe ControlMaster en el manual de ssh_config como una opción que permite varias sesiones sobre una sola conexión de red, y describe ControlPersist como la opción que mantiene el master abierto en segundo plano. Lee ambas descripciones juntas. ControlPersist no es solo una opción de rendimiento. Cambia el periodo durante el cual un proceso local puede solicitar nuevos canales en una conexión autenticada.
Para una persona que trabaja en un equipo, ese intercambio puede ser razonable. Para una automatización limitada a una tarea, necesita un responsable y una acción explícita de cierre.
Un evento de autenticación no es un evento de comando
Los equipos suelen mezclar autenticación, conexión, canal y comando. En SSH son cosas distintas, y la multiplexación deja clara esa diferencia.
El master inicial crea una conexión TCP con el servidor, verifica el host, negocia la criptografía y autentica la cuenta. Una vez autenticado, puede abrir canales SSH. Un comando de shell, un shell interactivo, una transferencia SFTP, una solicitud de reenvío de puerto local y una solicitud de reenvío de puerto remoto usan canales o solicitudes relacionadas con canales dentro de ese transporte.
Una nueva invocación local de ssh que encuentra un socket de control utilizable envía una solicitud a través de él. El master decide si abre el canal solicitado. No necesita volver a recibir la clave privada. No necesita otra solicitud del agente. El servidor tampoco recibe otro intento de inicio de sesión.
Esto no significa que OpenSSH guarde una contraseña reutilizable en el socket. Esa explicación es imprecisa y conduce a análisis deficientes. El problema de seguridad es un transporte ya autenticado con una interfaz local que puede pedirle que actúe. Un atacante que pueda usar el socket quizá no necesite la credencial original.
La diferencia cambia las preguntas de un incidente. «¿La clave seguía en el disco?» importa, pero no determina si el acceso continuaba. Pregunta lo siguiente:
- ¿Quedó un proceso master conectado al destino remoto?
- ¿Podía otro proceso local alcanzar su socket de control?
- ¿El master aceptó sesiones adicionales, transferencias de archivos o solicitudes de reenvío?
- ¿Quién podía ejecutarse como propietario del socket o atravesar el directorio del socket?
- ¿Cuándo terminó realmente el master?
Un ejecutor de tareas que informa del código de salida de un comando no responde por sí solo a ninguna de esas preguntas. El código solo describe el proceso hijo que esperó.
Los sockets compartidos convierten los límites entre procesos locales en límites de acceso
Un socket de control es un socket de dominio Unix local. Su ubicación y sus permisos en el sistema de archivos determinan qué procesos locales pueden intentar comunicarse con el master. Por eso un ControlPath demasiado amplio puede unir trabajos independientes aunque los alias de los hosts remotos parezcan ordenados.
Considera una cuenta de un ejecutor de CI con esta configuración:
Host *
ControlMaster auto
ControlPath /tmp/ssh-%r@%h:%p
ControlPersist 1h
El trabajo A se conecta como deploy a app.internal. Crea un master y un socket en /tmp. Después termina. El trabajo B, que se ejecuta con la misma cuenta local, se conecta al mismo destino. Puede reutilizar el transporte autenticado del trabajo A si puede conocer y acceder a ese socket.
Esta configuración es popular porque acelera los comandos repetidos casi sin cambiar la aplicación. Es incorrecta para trabajos independientes porque el límite de reutilización se basa en el host, el puerto y el usuario, no en el trabajo que posee la autorización. Una cuenta compartida del ejecutor convierte el error en acceso habitual entre tareas.
El directorio importa tanto como el archivo de socket. En los sistemas Unix, un proceso necesita permiso de búsqueda en el directorio para alcanzar una ruta. Un socket guardado en un directorio privado, con propietario restringido y modo 0700, ofrece un límite mucho más claro que un directorio temporal compartido. Los permisos del socket también importan, pero no debes tratarlos como el único control.
Usa un directorio creado para la tarea y propiedad de la cuenta que la ejecuta. Un wrapper de shell puede hacerlo sin depender de una configuración SSH global:
set -eu
run_id="release-4821"
cm_dir="$HOME/.ssh/task-control/$run_id"
mkdir -p "$cm_dir"
chmod 700 "$cm_dir"
socket="$cm_dir/%C"
ssh -o ControlMaster=auto \
-o ControlPersist=5m \
-o ControlPath="$socket" \
build-box 'id && hostname'
El token %C evita un nombre literal demasiado largo y reduce las colisiones entre conjuntos distintos de parámetros de conexión. El manual de ssh_config de OpenSSH lo define como un hash de los detalles de la conexión. Es útil, pero no incluye el ticket de despliegue, la sesión del agente ni el ID de la tarea. En este ejemplo, el directorio padre aporta ese límite que falta.
No coloques un socket de control literal en un checkout de un repositorio, en un espacio de trabajo con permisos de escritura amplios ni en /tmp solo porque esos lugares sean cómodos. La comodidad es la razón por la que un socket local se convierte en una capacidad compartida por accidente.
ControlPersist es una política de retención, no un plan de limpieza
ControlPersist indica a SSH que mantenga disponible un master después de cerrar las sesiones cliente. Acepta yes, que lo mantiene ejecutándose indefinidamente en segundo plano, o un periodo como 10m. Ambas opciones son decisiones de retención.
Un tiempo de espera ayuda cuando un cliente se bloquea antes de la limpieza. No demuestra que la tarea terminó al mismo tiempo que el acceso. Durante ese periodo, un proceso con acceso al socket puede solicitar nuevo trabajo. Una hora es especialmente difícil de justificar cuando una tarea de despliegue duró dos minutos.
Hay casos en los que un tiempo breve es razonable. Un proceso de automatización controlado puede ejecutar varios comandos seguidos y beneficiarse de una conexión ya preparada. En ese caso, haz que el tiempo sea menor que el intervalo inactivo esperado, aísla el socket para esa ejecución y termina el master cuando la tarea concluya correctamente. Así, el tiempo de espera funciona como respaldo ante un fallo, no como mecanismo normal de cierre.
La opción ControlPersist yes necesita un responsable con un motivo duradero para mantener el acceso. Un equipo de trabajo interactivo de un administrador puede tenerlo. Una tarea desechable no.
Otro error frecuente es suponer que un fallo del comando cierra el master. Un shell puede regresar antes después de que falle un comando remoto mientras el master en segundo plano sigue activo. Una cancelación de la tarea puede producir el mismo resultado. La limpieza debe ejecutarse después del éxito, del fallo y de una interrupción, y debe registrar si el cierre funcionó.
Si tu automatización usa un trap, mantén la limpieza limitada y verifica su objetivo. No elimines primero la ruta del socket sin comprobar nada. Quitar una ruta puede dificultar la investigación mientras el proceso master y la conexión siguen existiendo.
cleanup() {
ssh -S "$socket" -O exit build-box >/dev/null 2>&1 || true
rmdir "$cm_dir" 2>/dev/null || true
}
trap cleanup EXIT HUP INT TERM
Este patrón pide al master que termine antes de intentar eliminar el directorio. Si ssh -O exit falla, conserva el directorio e investiga en lugar de borrar las evidencias. En código de producción, escribe el fallo, el ID del proceso y la ruta del socket en el registro de la tarea.
exit y stop tienen significados operativos distintos
OpenSSH expone comandos de control mediante ssh -O. Los operadores suelen usar el incorrecto porque ambos nombres parecen relacionados con la limpieza.
ssh -O check host pregunta si hay un master en ejecución e informa de su ID de proceso cuando responde uno. ssh -O exit host pide al master que termine. Para una tarea completada, exit suele ser el comando adecuado porque termina el transporte reutilizable.
ssh -O stop host indica al master que deje de aceptar nuevas sesiones multiplexadas. Las sesiones existentes continúan. Puede ser útil cuando un operador necesita dejar que el trabajo activo termine, pero no permite afirmar que todo el acceso SSH terminó al completarse la tarea. Un master activo con canales activos sigue teniendo una conexión de red activa.
Usa explícitamente la ruta del socket cuando el diagnóstico no deba depender de la configuración actual del usuario:
ssh -S "$socket" -O check build-box
# Master running (pid=41782)
ssh -S "$socket" -O exit build-box
# Exit request sent.
ssh -S "$socket" -O check build-box
# Control socket connect(...): No such file or directory
Las palabras y el texto exacto de los errores varían según la plataforma y la versión de OpenSSH. Captura tanto la salida estándar como la de error en lugar de interpretar una frase como un contrato. La forma de las evidencias importa: una comprobación correcta identifica un master activo, una salida correcta envía la solicitud de terminación y una comprobación posterior fallida respalda la afirmación de que ningún socket de control respondió.
Hay un caso incómodo que conviene tener en cuenta. El socket puede permanecer después de que el master se bloquee, y un ID de proceso puede desaparecer entre dos comprobaciones. Una ruta obsoleta no demuestra que el acceso siga existiendo. A la inversa, una comprobación sin respuesta no demuestra que la conexión remota terminó si eliminaste la ruta antes de examinarla. Comprueba la tabla de procesos y los sockets Unix abiertos antes de borrar nada.
En macOS, lsof suele ser la herramienta local de inspección más rápida:
lsof -nP -U | grep '/.ssh/task-control/'
ps -p 41782 -o pid=,ppid=,lstart=,etime=,command=
Guarda la salida en el registro de la tarea. El primer comando relaciona un socket Unix con un proceso. El segundo aporta el proceso padre, la hora de inicio, el tiempo transcurrido y los detalles de la invocación. Trata grep como una ayuda interactiva, no como un control de auditoría. Un recopilador real debe consultar y guardar directamente los registros relevantes.
El reenvío de puertos hace más importante a un master inactivo
Un master que parece inactivo todavía puede mantener un estado de reenvío o aceptar después una solicitud de reenvío. Por eso contar únicamente los comandos de shell ofrece una visión incompleta.
El reenvío local expone un listener local que envía tráfico a través de la conexión SSH. El reenvío remoto pide al servidor que escuche y devuelva las conexiones a través del cliente. El reenvío dinámico crea un proxy SOCKS. Cada uno puede durar más que el comando que lo estableció, según cómo se hayan invocado la sesión y el master.
Los manuales de OpenSSH documentan comandos de control como forward y cancel para las solicitudes de reenvío cuando la multiplexación está activa. Esto resulta útil en operaciones. También significa que un proceso que alcance el socket puede solicitar rutas de red que van más allá de un comando de shell normal, según la política del servidor y el estado del master.
En tareas automatizadas, trata el reenvío como una excepción explícita. Registra la dirección de enlace, el puerto local o remoto, el host y el puerto de destino y el resultado de la limpieza. No permitas que un wrapper SSH genérico herede silenciosamente directivas LocalForward, RemoteForward o DynamicForward de una configuración amplia Host * del usuario.
Inspecciona la configuración resuelta antes de confiar en un alias:
ssh -G build-box | grep -E '^(controlmaster|controlpath|controlpersist|localforward|remoteforward|dynamicforward) '
ssh -G muestra la configuración efectiva después de que OpenSSH aplique las coincidencias de host y los valores predeterminados. Esta comprobación detecta un fallo sorprendentemente común: una tarea usa un alias sencillo, pero un archivo de configuración incluido activa la multiplexación o el reenvío lejos de la configuración propia de la tarea. El comando puede mostrar más campos en distintas versiones. Guarda la salida completa de ssh -G, no solo las líneas que esperabas.
Un sistema remoto puede registrar una única conexión de origen mientras varias conexiones de aplicaciones reenviadas pasan por ella. La telemetría de red, los registros SSH del servidor y los registros de la tarea responden a partes distintas de la historia. Ninguno puede sustituir a los demás.
Los registros del servidor por sí solos no pueden reconstruir la secuencia de decisiones locales
El servidor SSH remoto ve el transporte y lo que su configuración de registro conserve sobre canales y comandos. No puede decirte de forma fiable por qué un proceso local recibió permiso para usar un socket de control, qué tarea era propietaria del directorio del socket ni si otro proceso local lo reutilizó después de que terminara la tarea original.
Esto no es una crítica a los registros del servidor. Operan en otro punto del sistema. sshd puede registrar eventos de autenticación y conexión. Un wrapper de comandos forzados o un subsistema de auditoría puede registrar comandos remotos. Esos registros siguen siendo útiles. De forma predeterminada, no identifican a cada cliente local de multiplexación porque el servidor puede ver todos los canales como parte de un único transporte ya autenticado.
Conserva evidencias a ambos lados del límite. Un registro útil de la tarea incluye:
- la configuración completa del cliente obtenida con
ssh -G, sin secretos; - el nombre y la dirección del host remoto, la cuenta, el resultado de la verificación de la huella del host y el ID inicial del proceso master;
- la ruta del socket de control, el directorio padre privado y las horas observadas de creación y salida;
- cada comando remoto, transferencia o operación de reenvío solicitada, con su estado de salida;
- el resultado de
checkantes de la limpieza, el resultado deexity la inspección posterior de procesos y sockets.
Añade un identificador de tarea al nombre del directorio y al registro, no a una ruta de socket global compartida por todas las tareas. El identificador conecta los eventos locales sin fingir que por sí solo es un límite de seguridad.
Aplicar un hash o firmar el registro después de recopilarlo ayuda a detectar modificaciones posteriores, pero no repara los eventos que faltan. Recopila los eventos del ciclo de vida a medida que ocurren. Un registro armado a partir del historial del shell después de un incidente es una evidencia débil, especialmente cuando intervienen masters en segundo plano y reintentos.
Para el trabajo controlado por agentes, distingue la intención del agente de la acción SSH ejecutada. «Desplegar la versión X» es una intención. ssh build-box 'sudo systemctl restart api' es una acción. El registro de reutilización del socket explica si esa acción obtuvo un transporte nuevo o viajó por uno existente. Son hechos de auditoría distintos.
Los sockets limitados a una tarea dan un responsable a la limpieza
El patrón más seguro para trabajo automatizado breve es sencillo: usa un directorio de socket de control único por tarea, permite la reutilización solo dentro de esa tarea y envía una solicitud explícita de exit antes de informar de que la tarea terminó.
Un wrapper práctico necesita un ciclo de vida claro. Debe crear el directorio con permisos restrictivos, escribir un registro antes de la primera conexión, ejecutar los comandos con el mismo ControlPath explícito, apagar el master, verificar el resultado y solo después eliminar el directorio vacío. Una tarea que no pueda verificar la limpieza debe informar de un fallo de limpieza aunque el comando remoto haya tenido éxito.
Este ejemplo usa mktemp para evitar tener que adivinar un nombre de directorio único:
set -eu
base="$HOME/.ssh/task-control"
mkdir -p "$base"
chmod 700 "$base"
cm_dir=$(mktemp -d "$base/run.XXXXXX")
chmod 700 "$cm_dir"
socket="$cm_dir/%C"
finish() {
status=$?
ssh -S "$socket" -O exit build-box >>"$cm_dir/cleanup.log" 2>&1 || \
printf '%s\n' 'master exit request failed' >>"$cm_dir/cleanup.log"
ssh -S "$socket" -O check build-box >>"$cm_dir/cleanup.log" 2>&1 || true
exit "$status"
}
trap finish EXIT HUP INT TERM
ssh -o ControlMaster=auto \
-o ControlPersist=2m \
-o ControlPath="$socket" \
build-box 'deployctl apply release-4821'
La opción breve de ControlPersist cubre el caso en que un proceso muere antes de ejecutar su trap. No debe interpretarse como permiso para que otra tarea reutilice el master. El directorio aleatorio bloquea esa reutilización porque la segunda tarea no conoce ni hereda la ruta de la primera.
Hay una corrección que debes hacer antes de adoptar exactamente este ejemplo. No registres en cleanup.log argumentos sensibles de comandos, valores del entorno ni datos privados copiados. Los registros de auditoría necesitan suficiente detalle para establecer quién hizo qué, pero no deben convertirse en un nuevo almacén de secretos. Redacta los argumentos en el límite del wrapper, donde todavía conoces su significado.
Sallyport dirige las acciones SSH mediante su helper sin estado incluido sp-ssh, mientras mantiene las claves SSH en su bóveda cifrada en lugar de exponerlas al agente. Esto elimina un fallo común en la gestión de credenciales, pero los equipos todavía deben definir el límite de la acción y conservar registros que indiquen cuándo terminó la ejecución.
Las opciones de conveniencia globales destruyen los límites entre tareas
Una sección global Host * suele activar la multiplexación para todos los shells humanos, scripts, repositorios y procesos hijos de automatización de una cuenta. Es demasiado amplia cuando la misma cuenta ejecuta tareas con aprobaciones o responsables distintos.
La opción puede llegar mediante archivos Include, herramientas de gestión de configuración, los archivos personales del desarrollador o una imagen de compilación. La línea de comandos también puede sobrescribirla. No deduzcas el comportamiento activo a partir de un único archivo de configuración visible. Usa ssh -G con el alias de host y el contexto de usuario exactos que utilizará la tarea.
Si un entorno de automatización no puede garantizar una ruta de control privada, desactiva la multiplexación para esa acción:
ssh -o ControlMaster=no \
-o ControlPath=none \
build-box 'maintenancectl status'
Esto exige una nueva conexión y autenticación en cada invocación. Es un coste razonable para acciones de gran impacto, accesos excepcionales o trabajos que cruzan límites de confianza y de tareas. La autenticación repetida proporciona un evento de autorización más claro y hace mucho menos ambigua la interpretación del ciclo de vida.
No confundas ControlMaster=auto con una garantía de que un proceso solo reutilizará su propia conexión. auto significa que el cliente intentará encontrar un master en la ruta configurada y creará uno si no lo encuentra. La ruta configurada decide qué conexión puede encontrar.
Algunos equipos sostienen que un socket compartido está bien porque todos sus trabajos se ejecutan con una sola cuenta de servicio. Ese argumento solo se sostiene si todos los trabajos con esa identidad Unix tienen la misma autoridad para alcanzar todos los destinos mediante esa cuenta y si el equipo acepta que un trabajo pueda heredar el transporte activo de otro. La mayoría de los entornos maduros no quieren realmente eso.
Un final limpio de la tarea necesita pruebas del cierre del transporte
No declares terminada una tarea SSH cuando regrese el último comando remoto. Declárala terminada cuando la tarea haya cerrado su master y registrado pruebas, o haya informado de que no pudo hacerlo.
El registro final debe indicar a un investigador si un comando posterior tenía alguna vía para reutilizar la conexión. Necesita la identidad de la tarea, la configuración SSH resuelta, la ruta del socket, los detalles del proceso master, los registros de actividad, los resultados explícitos de los comandos de control y la observación posterior a la limpieza. También necesita evidencias remotas para los cambios que continúen de forma independiente de SSH, como el reinicio de un servicio o un proceso separado.
Establece un procedimiento que los operadores puedan ejecutar bajo presión: aísla el socket, inspecciónalo antes de limpiarlo, solicita exit, verifica que ningún master responda y conserva el resultado. Ese procedimiento es más útil que un tiempo de espera predeterminado largo porque convierte una suposición sobre el acceso en un hecho comprobable.
Cuando el trabajo sea lo bastante sensible como para que no resulte aceptable dejar un transporte autenticado, no lo optimices con un master compartido. Abre una conexión nueva, realiza la acción, ciérrala y conserva el registro. Los segundos adicionales cuestan menos que explicar por qué una ruta de acceso sobrevivió después de que la tarea debía haber terminado.
FAQ
¿Puede SSH ControlMaster mantener activa una conexión autenticada después de que termine mi comando?
Sí. Una conexión ControlMaster se autentica una vez y, después, otros clientes SSH pueden pedir al master local que abra nuevos canales. Si ControlPersist mantiene el master activo, esos canales pueden abrirse después de que hayan terminado el comando original y la tarea que lo inició.
¿Cuál es la diferencia entre una conexión SSH master y una slave?
El proceso master es la conexión SSH original que controla el transporte de red y el socket de control local. Un proceso slave es un comando ssh posterior que contacta con ese socket y pide al master que abra una sesión, un reenvío u otro canal.
¿Cómo cierro de forma segura un socket de control SSH compartido?
Usa ssh -O exit alias-del-host cuando tu configuración resuelva el alias hacia el ControlPath previsto. Si necesitas indicar directamente el socket, usa ssh -S /ruta/al/socket -O exit host. Comprueba primero con -O check para no cerrar por error la conexión equivocada.
¿Es seguro usar la multiplexación SSH en automatizaciones?
Reduce las autenticaciones repetidas y hace mucho más rápidos los comandos SSH breves. No es seguro cuando nadie se encarga de su ciclo de vida, cuando varias tareas independientes comparten un directorio de sockets o cuando una tarea puede seguir abriendo canales después de que haya expirado el límite de aprobación.
¿Los registros del servidor SSH muestran todos los comandos enviados mediante multiplexación?
El servidor SSH remoto suele registrar un único inicio de sesión del transporte y puede no identificar cada comando local posterior como un nuevo evento de autenticación. Si necesitas una explicación sólida de la actividad, recopila registros de procesos del cliente, la configuración de multiplexación, los eventos del ciclo de vida del socket de control y los registros de comandos de la tarea.
¿Debo confiar en el tiempo de espera de ControlPersist para limpiar SSH?
Un tiempo de espera es un límite superior, no una prueba de que la conexión terminó cuando acabó la tarea. Usa ssh -O exit durante la limpieza y confirma después que el socket desapareció y que ssh -O check falla. El tiempo de espera sigue siendo un respaldo útil si el cliente se bloquea.
¿Dónde deben guardarse los sockets de control SSH?
Trata los sockets de control como credenciales locales. Guárdalos en un directorio privado propiedad de la cuenta que ejecuta la tarea, usa una ruta independiente por tarea, evita los directorios temporales compartidos y elimina las rutas obsoletas solo después de comprobar si todavía pertenecen a un proceso master.
¿Qué significa %C en SSH ControlPath?
ControlPath define la ruta del socket Unix. %C se sustituye por un hash derivado de los atributos de la conexión y evita muchos problemas de longitud y colisiones de nombres, pero por sí solo no crea una identidad independiente para cada tarea.
¿Cerrar el master SSH deshace el trabajo iniciado en el host remoto?
No. Cerrar un transporte multiplexado termina esa conexión autenticada del lado del cliente, pero no deshace los archivos modificados remotamente, no termina un proceso remoto separado ni revoca credenciales que todavía pueda utilizar otra sesión. La limpieza del trabajo remoto debe hacerse por separado.
¿Qué evidencias debemos conservar cuando termina una tarea SSH automatizada?
Empieza con un registro reproducible: la configuración SSH completamente resuelta, el ID del proceso master, la ruta del socket, el destino remoto, las horas de inicio y salida y el resultado de un comando de cierre explícito. Conserva ese registro junto con la transcripción de comandos de la tarea y los registros relevantes del host remoto.