# Identidad del ejecutable de un agente después de sustituir un enlace simbólico

Un nombre de archivo sirve para localizar algo, no para identificarlo. La diferencia parece una precisión académica hasta que un agente recibe aprobación mientras se ejecuta como `~/bin/agent`, alguien cambia esa ruta por otro programa y la puerta de enlace decide que la siguiente solicitud es de confianza porque la etiqueta sigue pareciendo conocida.

Los enlaces simbólicos facilitan reproducir el error, pero no son su causa fundamental. Cualquier forma de indirección mutable puede provocarlo: un envoltorio de shell, una entrada de PATH, un binario copiado, una copia de desarrollo o un lanzador que resuelve una ruta en cada solicitud. Una puerta de enlace que aprueba un nombre de ruta ha aprobado un objeto que a menudo puede sustituir un usuario sin privilegios.

En macOS, la unidad útil es el proceso en ejecución y su identidad de código. Apple Code Signing Services puede describir y validar el código asociado a un proceso. La ruta puede aparecer en el registro para facilitar el diagnóstico, pero no puede decidir la autorización.

## Un enlace simbólico es un servicio de nombres, no el programa

Un enlace simbólico guarda una cadena que el kernel resuelve cuando un programa lo abre o lo ejecuta. Sustituir esa cadena cambia lo que encontrará una llamada `exec` posterior. No modifica la imagen ejecutable de un proceso que ya ha comenzado.

Ese detalle da una falsa sensación de seguridad. Se prueba una sustitución de enlace simbólico, se ve que el proceso original sigue funcionando y se concluye que el ataque no tiene importancia. La prueba solo ha mostrado el comportamiento normal de un proceso. No ha probado la decisión relevante: si la puerta de enlace reconoce un proceso posterior iniciado desde el alias como el mismo proceso que aprobó antes.

Considera un agente de programación iniciado mediante esta ruta:

```text
~/bin/build-agent -> ~/work/approved-agent
```

La puerta de enlace recibe la primera solicitud, ve `~/bin/build-agent` y pide permiso al operador. Después, un atacante cambia el enlace:

```text
~/bin/build-agent -> ~/Downloads/replacement-agent
```

Si el proceso aprobado sigue haciendo solicitudes, la puerta de enlace puede continuar correctamente esa sesión hasta que el proceso termine. Ese no es el fallo. El fallo aparece cuando se inicia otro proceso con el mismo nombre y la puerta de enlace dice: «Ya aprobé `~/bin/build-agent`.»

El segundo proceso es un sujeto de seguridad nuevo. Necesita una inspección nueva y, salvo que coincida con una regla de actualización definida de forma intencionada, una aprobación nueva.

El mismo error aparece sin enlaces simbólicos. Una puerta de enlace que ejecuta `codesign` sobre la ruta proporcionada por un cliente puede inspeccionar un archivo mientras acepta una solicitud de otro proceso. El cliente puede aprovechar una condición de carrera cambiando la ruta después de la inspección, o simplemente apuntar la puerta de enlace a un alias estable cuyos destinos cambian. Una ruta de aspecto correcto en una tarjeta de aprobación no arregla ese diseño.

Apple trata los requisitos de código como restricciones de identidad, no como nombres de archivo. Su documentación indica que un designated requirement contiene los criterios para decidir si un código es el mismo que se vio antes. Normalmente se deriva de la autoridad de firma y del identificador integrado cuando el desarrollador no ha declarado uno. Esa es la dirección correcta, con una salvedad importante: un requisito de código describe la identidad del código, no una sesión de proceso activa.

## El proceso en ejecución es el objeto que obtuvo la aprobación

Una puerta de enlace debe tomar la decisión de autorización contra una referencia a un proceso activo obtenida del sistema operativo. Después debe mantener esa decisión ligada a la ejecución del proceso, no a una ruta afirmada por el cliente, a un nombre de ejecutable ni a una variable de entorno reutilizable.

En macOS, una buena inspección tiene dos objetivos:

1. Obtener la información de firma del proceso que realmente hace la solicitud.
2. Validar ese proceso antes de usar la información de identidad como prueba de autorización.

Son tareas distintas. `SecCodeCopyDesignatedRequirement` puede devolver un designated requirement para código firmado, pero Apple señala expresamente que la llamada no valida la firma. Un código modificado después de firmarse o firmado de forma incorrecta todavía puede producir información incompleta o engañosa. La puerta de enlace también debe comprobar su validez.

El registro del proceso debe incluir pruebas suficientes para explicar la decisión más adelante:

- identificador del proceso o, mejor aún, una credencial del proceso emitida por el sistema operativo que resista la reutilización de PID
- ruta ejecutable observada durante la inspección, conservada solo como contexto
- identificador de firma e identificador de equipo cuando estén disponibles
- autoridad de firma y cadena de certificados cuando corresponda
- designated requirement
- cdhash o el conjunto de valores cdhash compatibles
- resultado y hora de la validación de la firma

No confundas esta lista con un lenguaje de políticas. La puerta de enlace necesita una respuesta concreta: qué proceso en ejecución hace la solicitud y si una persona aprobó esta ejecución. Unos pocos datos de identidad permiten responder. Un montón de reglas de rutas no.

La parte incómoda es gestionar las actualizaciones. Un designated requirement suele mantenerse estable entre versiones legítimas. Por eso macOS lo usa para conservar la continuidad cuando un usuario autoriza a una aplicación a acceder a un servicio protegido. La Nota Técnica TN3127 de Apple ofrece el ejemplo conocido de una aplicación que vuelve a usar el micrófono después de una actualización: macOS compara la nueva versión con el designated requirement registrado.

Ese comportamiento tiene sentido para un permiso duradero del sistema operativo. Es demasiado amplio si reutilizas en silencio una aprobación humana anterior para una ejecución nueva de un agente autónomo. Una puerta de enlace puede mostrar la autoridad de firma para ayudar al operador a reconocer el programa y, aun así, volver a pedir aprobación cuando se inicie un proceso nuevo. El firmante establece la procedencia. El límite del proceso establece la duración del consentimiento.

## Las comprobaciones estáticas de rutas dejan una condición de carrera imposible de justificar

Una comprobación estática responde a una pregunta estática: «¿Qué firma tiene ahora mismo este archivo en esta ruta?» Por sí sola no puede responder: «¿Qué código emitió esta solicitud?»

La diferencia importa incluso cuando la comprobación estática es técnicamente correcta. Supón que una puerta de enlace recibe una solicitud que indica que su ruta ejecutable es `/Users/dev/bin/agent`. Ejecuta:

```sh
codesign --verify --strict --verbose=2 /Users/dev/bin/agent
codesign -d -r- /Users/dev/bin/agent
```

Ambos comandos pueden informar de una firma válida y de un designated requirement. La puerta de enlace guarda entonces el nombre de ruta como identidad aprobada. Entre la comprobación y la siguiente solicitud, un atacante puede redirigir `agent` a otro archivo. En la siguiente solicitud, la puerta de enlace puede volver a ejecutar los comandos y obtener datos sobre el reemplazo. Ninguno de los dos demuestra una conexión con el proceso que hizo la solicitud.

Hay una segunda condición de carrera que los equipos suelen pasar por alto. Un componente auxiliar puede inspeccionar una ruta antes de iniciar un agente y recibir después una conexión de un proceso hijo. La comprobación de ruta describe el archivo padre en el momento del inicio. La conexión describe un proceso en el momento de la solicitud. Si el componente auxiliar no vincula ambos eventos mediante una credencial del sistema operativo, ha creado una suposición y la ha llamado procedencia.

Las APIs de firma de código de Apple exponen categorías de información separadas por este motivo. `kSecCSSigningInformation` solicita datos de certificados y CMS, `kSecCSRequirementInformation` solicita requisitos y `kSecCSDynamicInformation` solicita información de validez dinámica del código en ejecución. La superficie de la API no convierte esas piezas en un diseño completo de puerta de enlace, pero deja clara la diferencia: la inspección de una firma estática y el estado del código en ejecución son entradas distintas.

El nombre de archivo sigue teniendo un lugar en la pantalla de aprobación. Los operadores necesitan ver si una solicitud procede de una copia de trabajo en `~/work/demo` o de una herramienta instalada en `/Applications`. Trátalo como contexto de la interfaz. El registro de autorización debe seguir ligado al solicitante identificado por el sistema operativo.

## Reproduce la sustitución sin tocar un agente real

Puedes demostrar el problema de las rutas con dos binarios pequeños en un directorio temporal. Esta prueba no necesita un token de producción, una clave SSH ni un paquete de aplicación modificado.

Crea un espacio de trabajo y compila dos programas que impriman marcadores diferentes. El código se mantiene deliberadamente sencillo porque lo que se prueba es el reemplazo, no la lógica del programa.

```sh
work="$(mktemp -d /tmp/agent-identity.XXXXXX)"
cd "$work"

cat > approved.c <<'EOF'
#include <cstdio.h>
#include <unistd.h>
int main(void) {
  printf("approved process pid=%d\n", getpid());
  fflush(stdout);
  sleep(60);
  return 0;
}
EOF

cat > replacement.c <<'EOF'
#include <cstdio.h>
#include <unistd.h>
int main(void) {
  printf("replacement process pid=%d\n", getpid());
  fflush(stdout);
  sleep(60);
  return 0;
}
EOF

clang approved.c -o approved-agent
clang replacement.c -o replacement-agent
codesign --force --sign - --identifier com.example.approved approved-agent
codesign --force --sign - --identifier com.example.replacement replacement-agent
ln -s "$work/approved-agent" agent
```

La firma ad hoc basta para una prueba local de funcionamiento, pero no tiene cadena de certificados. Apple documenta que el código firmado ad hoc no tiene certificados y contiene datos CMS vacíos. No uses una firma ad hoc para simular un firmante de distribución ni para decidir qué acepta tu puerta de enlace de producción.

Registra a qué resuelve el alias, inspecciona su firma e inicia el primer proceso:

```sh
printf 'alias before: %s\n' "$(readlink agent)"
codesign -d -r- agent 2>&1 | sed -n '1,8p'
./agent &
first_pid=$!
printf 'first pid: %s\n' "$first_pid"
ps -p "$first_pid" -o pid=,comm=,args=
```

La forma de la salida debería parecerse a esta:

```text
alias before: /tmp/agent-identity.xxxxxx/approved-agent
Executable=/tmp/agent-identity.xxxxxx/agent
designated => identifier "com.example.approved"
approved process pid=48291
first pid: 48291
48291 ... ./agent
```

Ahora sustituye el enlace mientras el primer proceso duerme:

```sh
rm agent
ln -s "$work/replacement-agent" agent
printf 'alias after: %s\n' "$(readlink agent)"
codesign -d -r- agent 2>&1 | sed -n '1,8p'
ps -p "$first_pid" -o pid=,comm=,args=
```

Ahora deberías ver `com.example.replacement` al inspeccionar `agent`, mientras `first_pid` sigue activo. Inicia el alias por segunda vez:

```sh
./agent &
second_pid=$!
printf 'second pid: %s\n' "$second_pid"
wait "$first_pid" "$second_pid"
```

El segundo proceso imprime `replacement process`. Los dos PID ejecutaron imágenes de programa distintas aunque ambos se iniciaron como `./agent`.

Este es el resultado que debería cambiar tu plan de pruebas de la puerta de enlace. Una prueba que solo pregunta si el PID antiguo siguió ejecutándose casi no ha demostrado nada. La prueba útil pregunta si la puerta de enlace trata el segundo PID como un solicitante nuevo y muestra su identidad observada antes de permitirle usar una acción aprobada.

## Una prueba de aprobación útil tiene tres ejecuciones distintas

No te conformes con una aprobación correcta y un comando de enlace simbólico. Necesitas tres ejecuciones porque cada una demuestra una propiedad diferente.

Primero, inicia el destino aprobado mediante el alias y haz una solicitud de acción. La puerta de enlace debe crear un registro de sesión y mostrar una aprobación que identifique al solicitante real de una forma que el operador pueda valorar. Registra el identificador de sesión, el PID, la ruta observada y los datos de firma de código.

Segundo, cambia el alias pero deja vivo el primer proceso. Haz que el primer proceso realice otra solicitud. Si tu modelo de sesión aprueba una ejecución de proceso hasta que termina, la puerta de enlace puede permitirla. La imagen ejecutable no ha cambiado. No etiquetes falsamente este resultado esperado como una vulnerabilidad.

Tercero, inicia un proceso nuevo mediante el alias sustituido y haz la misma solicitud. La puerta de enlace no debe heredar la aprobación del primer proceso solo porque coincidan el nombre del ejecutable, la línea de comandos, el directorio de trabajo o la acción solicitada. Debe crear una sesión separada y exigir el flujo normal de autorización.

Usa una tabla de resultados mientras ejecutas la prueba:

| Ejecución | Destino del alias al iniciar | Resultado esperado |
| --- | --- | --- |
| Primer proceso | `approved-agent` | Nueva aprobación y después permiso durante esa ejecución |
| Primer proceso tras la sustitución | `replacement-agent` en el disco, pero la imagen antigua sigue en memoria | El comportamiento de la sesión existente continúa hasta la salida |
| Segundo proceso | `replacement-agent` | Nueva aprobación o denegación, nunca una aprobación heredada |

Hay otro caso que conviene probar. Vuelve a cambiar el alias al binario original y después inicia un tercer proceso. La puerta de enlace también debe crear una sesión nueva. La misma identidad de código no significa el mismo proceso. Si tu producto ofrece deliberadamente una relación de confianza persistente para un editor firmado, conviértelo en una decisión explícita y separada del operador. No la introduzcas de forma oculta en una función de aprobación por proceso.

La autorización por sesión de Sallyport está activada de forma predeterminada, y su tarjeta de aprobación comienza con la autoridad de firma del proceso, no con un nombre de archivo mutable. Eso proporciona al operador un dato más sólido para evaluar cuando un proceso nuevo del agente solicita actuar.

## El firmante, el designated requirement y el cdhash responden preguntas distintas

Los equipos suelen resumir tres conceptos distintos con la frase «firmada por la misma aplicación». Ese atajo produce cansancio por las aprobaciones o permisos que duran demasiado.

Una autoridad de firma responde quién firmó el código. En el software distribuido, puede incluir una cadena de certificados y un identificador de equipo. Ayuda al operador a distinguir un proveedor conocido de un ejecutable aleatorio. No identifica una compilación exacta.

Un designated requirement responde si macOS debe considerar que un código mantiene la misma identidad a través de las actualizaciones. Apple indica que todo código firmado tiene un designated requirement, explícito o sintetizado. El valor predeterminado normalmente incorpora la autoridad de firma y el identificador integrado. Eso lo hace adecuado para la continuidad, pero puede aceptar versiones posteriores que no hayas inspeccionado personalmente.

Un cdhash identifica un CodeDirectory firmado concreto. Para fines de autorización, se parece a la huella de una compilación. Es una prueba de auditoría excelente porque permite distinguir dos versiones que comparten firmante e identificador. Suele ser una mala regla de aprobación permanente para herramientas de desarrollo, porque las actualizaciones normales lo cambiarán.

Usa las pruebas según la decisión:

- Usa la credencial del proceso activo para vincular una sesión a un solicitante.
- Muestra la autoridad de firma y el identificador en la interfaz de aprobación para que el operador pueda reconocer el origen.
- Registra el designated requirement y el cdhash para que una investigación posterior pueda distinguir la continuidad del editor de la compilación exacta.
- Vuelve a pedir aprobación para un proceso nuevo del agente aunque coincidan el firmante y el designated requirement.

El último punto es deliberadamente más estricto que los permisos de macOS. Una puerta de enlace de agentes puede emitir solicitudes HTTP o comandos SSH con credenciales que nunca entran en el proceso del agente. Es un límite de acción, no una solicitud única de micrófono. Reutilizar una aprobación anterior para un proceso futuro no relacionado debilita el control humano que justificó la puerta de enlace desde el principio.

## No conviertas un monitor de archivos en autorización

Una respuesta tentadora consiste en vigilar la ruta del agente y revocar el permiso cuando cambia el archivo. Es popular porque parece sencilla: guardar el inode original, suscribirse a eventos del sistema de archivos e invalidar la aprobación después de una escritura o un cambio de nombre.

Es una base equivocada.

Los eventos del sistema de archivos sirven como telemetría, no como prueba de la identidad del solicitante. Las rutas pueden tener varios nombres. Un binario se puede copiar. Un proceso puede iniciarse desde un archivo desvinculado. La entrega de eventos puede retrasarse o agruparse. Un atacante no necesita ganar una carrera espectacular si tu diseño ya autoriza la ruta en lugar del proceso.

Comparar inodes tampoco arregla el modelo. Puede ayudar a detectar un reemplazo concreto en un directorio concreto, pero no dice nada útil sobre un proceso iniciado desde otro enlace duro o desde un destino copiado. También crea un comportamiento frágil en flujos de desarrollo normales, donde las herramientas recompilan, cambian de nombre y sustituyen archivos constantemente.

Trata las observaciones de archivos como un motivo para añadir contexto útil a la auditoría. Si la ruta actual apunta a un destino diferente del que tenía al aprobarse, registra ese hecho. Si la puerta de enlace ve un proceso solicitante nuevo, inspecciónalo y aplica el flujo normal de autorización. El límite del proceso gestiona la decisión de seguridad sin confiar en que el monitor actúe como un monitor de referencia.

Esta regla también evita un error más sutil: autorizar un envoltorio porque su firma parece conocida e ignorar lo que inicia. Un envoltorio firmado puede ejecutar un hijo sin firma, cargar un script local o seleccionar un destino según su entorno. La puerta de enlace debe identificar el proceso que realmente se conecta y solicita la acción privilegiada. Si el envoltorio es el solicitante, ese es el objeto que apruebas. Si el solicitante es su hijo, inspecciona al hijo.

## Vincula la aprobación a una credencial del sistema operativo y rechaza las ambigüedades

Una implementación práctica necesita un identificador proporcionado por el sistema operativo para el solicitante. En macOS, normalmente esto significa obtener un objeto de código invitado para el proceso solicitante mediante Code Signing Services, usando atributos del proceso proporcionados por el contexto de conexión confiable, no valores copiados de la entrada del agente.

La secuencia segura es esta:

1. Acepta una conexión y obtén la identidad del solicitante desde el mecanismo de conexión del sistema operativo.
2. Resuelve esa identidad en un objeto de código en ejecución.
3. Comprueba la validez del código antes de leer los datos de identidad para autorizar.
4. Lee la autoridad de firma, el identificador, el designated requirement y el cdhash de ese objeto de código en ejecución.
5. Crea un registro de sesión ligado a la credencial del solicitante y exige aprobación si no existe una sesión aprobada.
6. Vuelve a comprobar antes de cada acción que la credencial del solicitante siga apuntando al mismo proceso activo. Destruye la sesión cuando el proceso termine o el operador la revoque.

Los detalles del primer paso dependen del transporte. Un socket Unix local, una conexión XPC y una tubería entre procesos hijos exponen credenciales distintas. El retorno peligroso es el mismo en todos los casos: aceptar un PID, una ruta, un identificador de paquete o un resumen de firma que el agente envía en el cuerpo de la solicitud. El agente controla ese contenido. No demuestra nada.

La reutilización de PID merece especial atención. Un PID aislado solo es único mientras el proceso existe. Después de su salida, el sistema operativo puede asignar el mismo número a otro proceso. Guarda una credencial más completa si la API te la proporciona. Si el transporte no puede ofrecer una vinculación duradera con el solicitante, limita la sesión a la duración de la conexión y vuelve a autorizar después de cada reconexión. Rechazar una solicitud por falta de comodidad es menos conveniente que aceptar un solicitante ambiguo, pero la ambigüedad es precisamente donde viven los errores de sustitución de rutas.

Mantén las solicitudes de aprobación claras. Si la puerta de enlace ve una firma ad hoc, dilo. Si no existe una firma válida, dilo. Si el programa tiene un nombre visible conocido pero una autoridad de firma nueva, no escondas la autoridad detrás de un triángulo de expansión. La primera línea debe decir al operador quién firmó el proceso y qué acción quiere realizar.

## El registro de auditoría debe conservar la decisión, no solo la solicitud

Una entrada de registro que dice que `POST /deploy` tuvo éxito no puede explicar un incidente de sustitución de enlace simbólico. Indica qué ocurrió, pero no por qué la puerta de enlace permitió actuar a ese solicitante.

Para cada sesión aprobada, guarda una instantánea de las pruebas de identidad observadas en el momento de la aprobación. Para cada acción, guarda la referencia de la sesión y el resultado. El registro de la acción no necesita copiar los datos del certificado repetidamente, pero debe llevar al registro exacto de aprobación que los contenía.

Un registro compacto podría tener este aspecto:

```json
{
  "session": "6F2A...",
  "caller": {
    "process": "OS-issued caller credential",
    "path_observed": "/private/tmp/demo/agent",
    "signing_identifier": "com.example.approved",
    "designated_requirement": "identifier com.example.approved",
    "cdhash": "<observed digest>",
    "validity": "valid"
  },
  "approval": "granted",
  "action": "HTTP POST /deploy",
  "result": "success"
}
```

La ruta sigue siendo útil. Puede indicar que un proceso se inició desde un directorio temporal o un alias de shell. No debe bastar para vincular una solicitud posterior con esta sesión.

Sallyport conserva un registro de sesiones para las ejecuciones de agentes y un registro de actividad para cada llamada, ambos proyectados desde un único registro de auditoría cifrado y encadenado mediante hashes. Su comando `sp audit verify` sin conexión comprueba la cadena sin necesitar la clave de la bóveda. Este diseño facilita revisar una prueba de sustitución de rutas porque el evento de aprobación y la acción posterior son hechos separados, no un único mensaje de éxito ambiguo.

Haz el ejercicio del enlace simbólico antes de escribir una regla de aprobación que después no puedas explicar. Si un reemplazo recién iniciado puede usar la aprobación del primer proceso, deja de tratar el nombre del ejecutable como un simple campo informativo. Ya se ha convertido en parte de la decisión de confianza.
