La reutilización de PID corrompe la atribución de sesiones de agentes
La reutilización de PID puede atribuir acciones de un agente al proceso equivocado. Crea sesiones de agentes más seguras en macOS con datos de inicio, comprobaciones del firmante, instantáneas del padre y una nueva validación estricta.

Un PID aislado puede servir para una pantalla de estado. No sirve como identidad detrás de una aprobación, un registro de auditoría o una sesión de agente con credenciales. El sistema operativo recicla los ID de proceso y un sistema de agentes que trate un número reutilizado como si fuera el mismo actor puede asociar un proceso posterior con la aprobación de otro anterior.
Este error no requiere un atacante con acceso al kernel ni una condición de carrera exótica. Aparece cuando termina un agente de corta duración, una máquina ocupada crea suficientes procesos para reutilizar su PID y algún componente vuelve a conectar una llamada, actualiza una pantalla o busca una fila antigua de auditoría por PID. El proceso posterior puede ser un programa completamente normal. La atribución sigue siendo incorrecta y un sistema de aprobaciones que actúa con una atribución equivocada ya ha perdido el sentido de tener aprobaciones.
La solución no es complicada, pero exige cambiar el vocabulario. Deja de llamar identidad de proceso a un PID. Guarda un registro del proceso capturado que contenga el PID, la hora de inicio, los datos de firma de código y la procedencia del padre, recopilados cuando tomaste la decisión de autorización. Después, haz que cada comparación posterior demuestre que el proceso activo todavía coincide con ese registro.
Un PID nombra una posición, no el ciclo de vida de un proceso
Un ID de proceso identifica una entrada del kernel asignada en ese momento. No nombra para siempre a un programa ni ofrece una garantía de unicidad global a lo largo del tiempo. Cuando el proceso termina, su PID puede reutilizarse.
La diferencia parece quisquillosa hasta que un sistema permite que una aprobación anterior siga vigente. Supón que un cliente agente se inicia como PID 4812, solicita usar una credencial de despliegue y la persona aprueba esa ejecución. El cliente termina. Más tarde, otro proceso recibe el PID 4812. Si tu puerta de enlace pregunta «¿el PID 4812 tiene una sesión aprobada?», puede recuperar la aprobación antigua y asociarla con el proceso nuevo.
Lo peligroso es que los procesos antiguo y nuevo no tienen que coincidir en el tiempo. Muchas implementaciones cometen el error en una unión de base de datos, una consulta de caché o una tarea de enriquecimiento tardía. Guardan una aprobación como pid = 4812, guardan las llamadas de la misma forma y confían en que una consulta posterior conserve el significado original. No lo hace.
Un PID puede seguir siendo útil en un registro dirigido a personas porque ayuda a inspeccionar un sistema activo. Consérvalo. Simplemente no lo conviertas en la identidad principal para autorizar o atribuir acciones.
Hay una segunda trampa: fork y exec complican las explicaciones simplistas. Fork crea un hijo nuevo con un PID distinto. Exec reemplaza la imagen del programa en un proceso existente y conserva su PID. Si apruebas un ejecutor de shell y este hace exec de otro binario, el PID sigue siendo conocido, pero el código que hará la siguiente solicitud puede ser diferente. Un diseño de identidad debe tener en cuenta tanto la reutilización del PID después de la salida como el reemplazo del programa dentro de un proceso que sigue activo.
Captura un registro del proceso al iniciar la sesión
Un registro de sesión fiable es una instantánea tomada en el límite de seguridad, no una colección de datos reconstruida después. Captúralo cuando el proceso del agente solicite crear una sesión por primera vez, antes de mostrar una aprobación que describa al solicitante.
Para una puerta de enlace de agentes en macOS, guardaría estos campos como una única instantánea inmutable del proceso:
- PID e ID de usuario efectivo.
- Hora de inicio del proceso con segundos y microsegundos cuando la plataforma los proporcione.
- Ruta del ejecutable observada al capturar los datos.
- Identificador de firma, contexto del equipo o del firmante y hash del directorio de código cuando estén disponibles.
- Instantánea del proceso padre, incluido su propio PID y hora de inicio.
La hora de inicio convierte un PID en una referencia específica de un ciclo de vida. Una tupla práctica sería:
process_instance = (
pid = 4812,
start_time = 2026-07-22T14:03:18.482911Z,
euid = 501
)
Esa tupla responde a una pregunta concreta: «¿Es este el mismo ciclo de vida del proceso del kernel que vi antes?». No responde si el proceso es de confianza. El registro de firma responde a otra pregunta: «¿Qué código validó macOS cuando inspeccioné este proceso?». La instantánea del padre responde a una tercera: «¿Qué creó este proceso, según lo observado al iniciar la sesión?».
No conviertas esas preguntas en una sola etiqueta de texto como Claude Code (PID 4812). La etiqueta puede estar bien en una tarjeta de aprobación, pero elimina los datos que necesitas cuando un revisor pregunta por qué se autorizó una sesión concreta.
En macOS, un inspector de procesos puede usar proc_pidinfo con PROC_PIDTBSDINFO para obtener proc_bsdinfo, que incluye pbi_start_tvsec y pbi_start_tvusec. Apple también expone start_time del proceso en su modelo de procesos de Endpoint Security. Lo importante no es qué API elijas, sino capturar la hora de inicio en el momento de decidir y compararla después.
Un ayudante C reducido para la parte del ciclo de vida podría verse así:
#include <libproc.h>
#include <sys/proc_info.h>
#include <cstdio.h>
int read_process_lifetime(pid_t pid) {
struct proc_bsdinfo info = {0};
int size = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0,
&info, sizeof(info));
if (size != sizeof(info)) {
return -1;
}
printf("pid=%d start=%lld.%06d parent=%d uid=%d\n",
info.pbi_pid,
info.pbi_start_tvsec,
info.pbi_start_tvusec,
info.pbi_ppid,
info.pbi_uid);
return 0;
}
La forma de salida importa más que el lenguaje utilizado:
pid=4812 start=1784738598.482911 parent=4760 uid=501
Si una lectura posterior devuelve el mismo PID con una marca de inicio diferente, estás viendo otro proceso. Rechaza la coincidencia de sesión, aunque todas las capas auxiliares quieran tratar el número como conocido.
La firma de código describe el código, no una instancia en ejecución
Los datos de firma de código aportan una procedencia útil, pero no se convierten por sí solos en una identidad de proceso. Un identificador de firma puede ser compartido por todas las versiones publicadas de una aplicación. Un identificador de equipo identifica a una organización firmante, no a un ejecutable concreto. Un hash del directorio de código es más específico del contenido firmado, pero muchas instancias simultáneas del mismo ejecutable seguirán compartiéndolo.
La documentación de firma de código de Apple establece una distinción importante que los productos de seguridad suelen difuminar. Varios firmantes pueden reclamar un identificador de firma, por lo que Apple recomienda comprobar la categoría de validación y, para código que no sea de Apple, también el identificador de equipo. La nota técnica TN3127 de Apple explica que un requisito designado combina un identificador con requisitos de firma para establecer la identidad del código entre actualizaciones.
Ese es el modelo mental correcto: los datos de firma indican qué ejecutable ha validado macOS y si cumple tu requisito de confianza. No indican si es el mismo proceso que la persona aprobó cinco minutos antes.
Para autorizar sesiones, usa ambas capas:
same_process_lifetime:
pid, start_time, euid all match the captured record
same_expected_code:
captured signing requirement still validates for the live process
same_session:
the session token refers to this captured process record, not only its PID
No compares solo un nombre visible, un identificador de paquete o una ruta. Las rutas cambian. Las herramientas de línea de comandos sin firma pueden tener propiedades de identidad débiles o limitadas al equipo local. Además, una persona puede ejecutar una copia de una herramienta conocida desde otra ubicación. La interfaz de aprobación puede mostrar una etiqueta sencilla, pero el código de autorización debe conservar los datos de firma evaluados que dieron lugar a esa etiqueta.
Una recomendación equivocada muy habitual dice que se debe autorizar únicamente por identificador de firma porque así se conservan las actualizaciones de la aplicación. Es popular porque la continuidad entre actualizaciones resulta cómoda. No sirve para una aprobación por ejecución. Si una persona aprobó un proceso de agente concreto, el siguiente lanzamiento del mismo programa firmado es una ejecución nueva y debe recibir una decisión de sesión nueva. La identidad estable del código ayuda a que la tarjeta de aprobación sea fácil de entender. No permite ampliar silenciosamente una aprobación de una ejecución a todas las instancias futuras.
El PID del padre solo es evidencia si conservas su ciclo de vida
Los datos del proceso padre ayudan a los revisores a entender cómo se inició un agente. Pueden distinguir entre un cliente lanzado desde una terminal y otro iniciado por un editor, un programador o un agente diferente. Pero ppid = 4760 tiene el mismo problema de reutilización que el PID del hijo.
El patrón incorrecto es fácil de detectar:
session.agent_pid = 4812
session.parent_pid = 4760
Tres horas después, un visor de auditoría busca el PID 4760 y encuentra un proceso con ese número, al que etiqueta como padre de la sesión. El padre original puede haber terminado hace mucho. Otro proceso es ahora el dueño del 4760. La página de auditoría ha convertido una afirmación histórica en una consulta en vivo y ha reescrito la historia sin avisar.
Captura la instantánea del padre junto con la del hijo:
parent_instance = (
pid = 4760,
start_time = 2026-07-22T14:01:02.117604Z,
euid = 501,
executable_path = "/usr/bin/login",
signing_requirement = "captured evaluation",
relationship = "observed_parent_at_session_open"
)
Ese último campo de relación puede parecer trivial, pero evita muchas descripciones incorrectas. El registro del padre significa «este era el padre directo cuando el sistema observó al hijo». No significa «este padre autorizó al hijo», «este padre será dueño del hijo para siempre» ni «este sigue siendo el padre actual». Esas afirmaciones requieren pruebas independientes.
Si tienes eventos de procesos de Endpoint Security, es_process_t de Apple proporciona el PID del padre original y expone parent_audit_token y responsible_audit_token, además de la hora de inicio y los datos de firma. Los tokens de auditoría son preferibles a los PID sin más cuando la API los ofrece, porque conservan más contexto. Aun así, trata el objeto de proceso de un evento como una observación capturada. No lo sustituyas por una consulta posterior del PID y la consideres equivalente.
En sistemas sin Endpoint Security, usa las mejores API de procesos disponibles, captura los datos rápidamente y muestra la incertidumbre. El padre puede terminar entre la lectura del hijo y la del padre. No inventes certeza en esa carrera. Marca el padre como no disponible o parcial, conserva la identidad del hijo que sí capturaste y evita afirmar una relación que no pudiste observar.
Vuelve a validar antes de que una sesión aprobada realice una acción
La aprobación de una sesión requiere gestionar la identidad en dos momentos: capturarla al abrir la sesión y volver a validarla cuando la sesión use la autoridad. La captura protege el registro de auditoría. La nueva validación protege la siguiente acción.
Imagina esta secuencia:
- Un proceso de agente abre una sesión y capturas el PID 4812, la hora de inicio, los datos del firmante y los detalles del padre.
- Una persona aprueba esa ejecución exacta del agente.
- El proceso termina mientras el token de sesión permanece en memoria o en una conexión IPC local.
- Un proceso posterior recibe el PID 4812 y presenta un token obsoleto o llega a una asociación antigua por un error.
- Tu puerta de enlace comprueba el proceso activo antes de realizar una llamada con credenciales.
En la comprobación final, el PID puede coincidir. La hora de inicio no lo hará. Esa diferencia debe cerrar la sesión. No corrijas el registro con la nueva marca temporal, no crees una sesión de reemplazo silenciosa y no atribuyas la acción al proceso anterior.
La comprobación también debe fallar de forma segura cuando la inspección no sea posible. Si tu código no puede leer los datos del proceso porque este terminó, cambiaron los permisos o el sistema operativo devolvió datos incompletos, no puede demostrar que el solicitante sea el proceso aprobado. La respuesta correcta es exigir una sesión nueva.
Mantén la comparación estrecha y literal. No uses coincidencias aproximadas como «mismo nombre de comando», «mismo directorio de trabajo» o «misma ventana de terminal». Esos campos ayudan a una persona a entender el contexto, pero no resisten la ambigüedad accidental o provocada. Un proceso puede cambiar su directorio de trabajo y dos procesos sin relación pueden elegir el mismo nombre de comando.
El lugar correcto para almacenar en caché es la decisión de autorización vinculada a la instancia de proceso capturada. El lugar equivocado es un mapa de estado de aprobación indexado por PID. Un mapa con el PID como clave pasará las pruebas en un portátil tranquilo y fallará cuando haya mucha rotación de procesos, lo que lo hace especialmente difícil de diagnosticar después de llegar a los usuarios.
Modela las sesiones como eventos inmutables, no como filas mutables de procesos
Un diario de sesiones debe conservar lo que la puerta de enlace observó en cada momento. Una fila mutable que siempre diga «el PID 4812 está activo» no puede responder si sus campos proceden del agente original, de un proceso de reemplazo o de una actualización en segundo plano realizada tarde.
Usa registros de eventos de solo adición con referencias explícitas. La estructura puede ser sencilla:
{
"event_type": "session_authorized",
"session_id": "sess_7d9f",
"process_instance": {
"pid": 4812,
"start_time": "2026-07-22T14:03:18.482911Z",
"euid": 501,
"signing_id": "com.example.agent",
"team_id": "A1B2C3D4E5",
"cdhash": "captured-code-directory-hash"
},
"parent_instance": {
"pid": 4760,
"start_time": "2026-07-22T14:01:02.117604Z"
},
"approval": {
"scope": "this process run",
"decision": "approved"
}
}
El siguiente evento de llamada hace referencia a sess_7d9f y registra su propia hora, el canal solicitado, el objetivo de la acción y el resultado. No copia únicamente pid: 4812 esperando que un analista pueda reconstruir el resto. Si la nueva validación falla, escribe un evento de denegación separado con la diferencia observada.
{
"event_type": "action_denied",
"session_id": "sess_7d9f",
"reason": "process_start_time_mismatch",
"captured_pid": 4812,
"captured_start_time": "2026-07-22T14:03:18.482911Z",
"observed_start_time": "2026-07-22T15:47:09.031882Z"
}
Esto proporciona algo concreto al revisor: el número de proceso se reutilizó, la puerta de enlace reconoció la diferencia y rechazó la solicitud. Sin ambas marcas temporales, el evento solo dice que algo falló. No basta cuando necesitas distinguir un defecto de software de un intento hostil de heredar autoridad.
La separación de Sallyport entre un registro de sesiones y un registro de actividad resulta útil porque la autorización a nivel de sesión y las acciones individuales responden a preguntas de auditoría diferentes. Ambos deben conservar la instantánea del proceso existente cuando se tomó la decisión de sesión, en lugar de depender de un PID aislado durante la proyección o la revisión posteriores.
La tarjeta de aprobación debe describir la procedencia sin fingir certeza
Las personas no pueden tomar una buena decisión ante un diálogo que solo dice «El agente quiere acceder». Necesitan suficiente contexto de identidad para reconocer al solicitante, pero no conviene llenar la tarjeta de campos que parecen precisos y aportan poca información.
Empieza por la autoridad de firma de código que evaluaste. Incluye el nombre o la ruta del ejecutable cuando ayude. Describe la relación del proceso con palabras sencillas, como «iniciado por una aplicación de terminal firmada» o «lanzado desde un componente auxiliar del editor», solo si capturaste los datos que lo respaldan. Muestra el PID como detalle para solucionar problemas, no como la identidad declarada.
Evita presentar una cadena de padres como si fuera una cadena de certificados. La relación entre padres es una observación del sistema operativo en un momento concreto. Un padre firmado puede iniciar un hijo sin firma. Un padre esperado puede ser un envoltorio que haga exec de otra cosa. La tarjeta de aprobación debe dejar claro quién solicita la sesión ahora y ofrecer la información del padre como contexto.
Esta distinción también cambia la revocación. Revoca un identificador de sesión, no un PID. Cuando una persona revoque la sesión, marca como revocado el registro inmutable y rechaza los intentos posteriores de acción que hagan referencia a él. Si el proceso original sigue activo, pierde el acceso. Si terminó y su PID se reutilizó, la revocación sigue siendo correcta porque nunca dependió de controlar ese PID numérico.
Una aprobación debe significar una ejecución observada. Si las personas quieren una decisión de confianza más amplia, crea una función explícita independiente con un alcance claro, como un requisito de código firmado y una duración definida. No introduzcas ese alcance más amplio en una aprobación por sesión porque la implementación tuviera una caché de PID conveniente.
Prueba el fallo en lugar de confiar en el comportamiento normal de los procesos
Los errores de reutilización de PID permanecen ocultos porque las pruebas manuales normales no generan suficiente rotación. La suite de pruebas debe obligar a tu código a demostrar que rechaza un proceso de reemplazo que heredó un número y que rechaza un exec que cambia el código detrás de un proceso que sigue activo.
No necesitas esperar a que macOS reutilice de forma natural un PID concreto. Coloca la capa de inspección de procesos detrás de una interfaz y aliméntala con instantáneas controladas. Una buena prueba tiene un registro aprobado y una observación posterior del proceso activo con el mismo PID, pero una hora de inicio distinta:
captured: pid=4812 start=1784738598.482911 signer=team-A
observed: pid=4812 start=1784744029.031882 signer=team-B
expected: deny with process_start_time_mismatch
Añade un segundo caso en el que coincidan el PID y la hora de inicio, pero falle el requisito de firma. Añade otro en el que el hijo siga activo, pero exec haya cambiado los datos del ejecutable. Estas pruebas demuestran que el código de autorización combina los campos correctos, en lugar de limitarse a comprobar que la inspección del proceso devuelve datos.
Prueba también la vista de auditoría por separado. Crea una sesión antigua con un PID de padre, simula que ese padre termina y proporciona después un proceso sin relación con el mismo PID. La sesión histórica mostrada debe conservar los campos capturados del padre original. No debe reemplazarlos por un nombre obtenido de la tabla de procesos actual.
Por último, prueba la cancelación y los fallos de inspección. Un proceso puede desaparecer durante el breve intervalo entre aceptar una solicitud IPC y consultar sus detalles. Tu código debe registrar un motivo claro de denegación y exigir al solicitante que establezca una sesión nueva. Los sistemas que convierten estos fallos en coincidencias aproximadas crean exactamente la ambigüedad que el modelo de identidad pretendía eliminar.
La invariante útil es sencilla y estricta
Toda acción privilegiada de un agente debe referirse a una sesión vinculada a un único ciclo de vida de proceso capturado. Una solicitud activa coincidente debe demostrar que procede de ese mismo ciclo de vida y que su ejecutable todavía cumple la identidad de código aprobada. Un PID reutilizado falla la primera prueba. Un binario diferente detrás de un PID sin cambios falla la segunda.
Esta invariante mantiene cada dato del proceso en su lugar. Los datos de inicio distinguen los ciclos de vida. Los datos del firmante identifican el código validado. Los detalles del padre aportan procedencia. El ID de sesión contiene la decisión humana. Ninguno de esos campos puede hacer el trabajo de los demás.
Sallyport puede hacer visible esa decisión mostrando primero la autoridad de firma de código del proceso al autorizar la sesión y conservando registros separados para las ejecuciones y las llamadas. Aun así, la implementación debe impedir que un número conveniente, por familiar que parezca en un registro, sustituya al proceso que obtuvo la aprobación.
La próxima vez que veas una tabla indexada por pid, pregunta qué ocurre cuando el proceso termina. Si la respuesta es «lo consultamos de nuevo», la tabla no almacena la identidad de una sesión. Corrígelo antes de que un evento normal del ciclo de vida de un proceso se convierta en un error de autorización.
FAQ
¿Por qué un ID de proceso no es una identidad de proceso única?
Un PID es una posición en la tabla de procesos del sistema operativo, no una identidad permanente. Cuando un proceso termina, macOS puede asignar el mismo número a otro proceso. Si tu registro solo dice «PID 8421», puede apuntar al proceso equivocado después de suficiente actividad.
¿Qué debo guardar junto con un PID para identificar un proceso de agente de forma segura?
Como mínimo, usa el PID junto con la hora de inicio del proceso. En un sistema de control de agentes, registra también el ID de usuario, la ruta del ejecutable capturada al conectarse, la información de firma y una instantánea del proceso padre. Cada campo detecta una forma distinta en que un PID aislado puede llevarte a error.
¿Bastan el PID y la hora de inicio para autorizar un agente?
No. La hora de inicio distingue dos ciclos de vida que recibieron el mismo PID, pero no indica si el ejecutable es de confianza. Combínala con información de firma validada y conserva el proceso padre como evidencia de procedencia.
¿Puedo confiar por sí solo en un identificador de firma de macOS?
Un identificador de firma de código nombra código dentro de un ámbito de firma, pero por sí solo no demuestra que el proceso sea el que querías aprobar. Una comprobación sólida incluye el contexto del firmante o del equipo y valida el requisito de código. En los registros forenses, conserva también el hash del directorio de código cuando la plataforma lo ofrezca.
¿Cómo registro el proceso padre sin errores por reutilización de PID?
Captura el proceso padre cuando el hijo se conecte o cuando observes el evento exec. No consultes más tarde el PID del padre suponiendo que el resultado representa la verdad histórica. Los PID de los padres pueden reutilizarse igual que los de los hijos.
¿Cómo evito que la reutilización de un PID apruebe al agente equivocado?
Trata la identidad del proceso como un dato de autorización, no como una etiqueta visual. Comprueba de nuevo el proceso activo justo antes de conceder una sesión, compara su hora de inicio y sus datos de firma con el registro capturado y rechaza la solicitud si el proceso terminó o cambió.
¿Endpoint Security resuelve la atribución de procesos en macOS?
Puede ser útil, pero no sustituye un registro de identidad que tenga en cuenta el ciclo de vida. Endpoint Security expone la hora de inicio del proceso, datos de auditoría, campos de firma de código y datos de auditoría del padre en sus estructuras de procesos. También requiere el entitlement adecuado y trabajo operativo que muchas aplicaciones de escritorio no necesitan.
¿Cuál es la diferencia entre un cdhash y la identidad de un proceso?
No. Un hash del directorio de código identifica el contenido del código firmado, mientras que la identidad de un proceso identifica una instancia concreta en ejecución. Diez procesos simultáneos pueden tener el mismo hash y un PID puede referirse a varios procesos distintos con el paso del tiempo.
¿Cómo debe modelar un registro de auditoría las sesiones de agentes y la salida de procesos?
Conserva eventos inmutables, no una única fila de sesión mutable que se sobrescriba. Guarda la identidad capturada al aprobar la sesión, añade registros de llamadas que hagan referencia a esa identidad y registra la revocación o la salida como eventos posteriores. Así los revisores tienen una secuencia que pueden examinar, en lugar de una suposición basada en el estado actual.
¿Puede exec cambiar un proceso de agente aprobado sin cambiar su PID?
Fork puede conservar un PID mientras exec reemplaza la imagen del programa en ese proceso. Esto significa que el PID e incluso la relación con el padre pueden seguir iguales mientras cambian el ejecutable y el firmante. Comprueba la identidad cuando el agente se conecte y de nuevo antes de usar una autorización de larga duración.