# ¿Pueden las instancias duplicadas de una pasarela dividir tu historial de auditoría?

Un paquete de aplicación copiado no es una forma inofensiva de crear una segunda pasarela local. Es una prueba para comprobar si el software puede mantener una única autoridad sobre las credenciales, las aprobaciones, las revocaciones y las pruebas cuando macOS le presenta dos candidatos plausibles.

El fallo peligroso no siempre es un bloqueo. Un bloqueo es evidente. El fallo más silencioso aparece cuando dos procesos parecen funcionar correctamente, aceptan solicitudes de agentes y dejan suficientes pruebas para tranquilizar a quien los inició. Después, un cambio de credencial, una revocación o una revisión de incidentes revela la división: un proceso sabía algo que el otro desconocía.

Sallyport está diseñado como una única aplicación de barra de menús para macOS, firmada y siempre activa, con el núcleo de la bóveda dentro del proceso. Por eso, iniciar a la vez los paquetes estable, beta y copiado debe tratarse como una prueba deliberada de concurrencia e identidad, no como una práctica normal. La prueba debe responder a una pregunta concreta: ¿el sistema rechaza el duplicado, lo coordina de forma segura o lo permite mientras conserva una única bóveda con autoridad y un historial verificable?

## Dos paquetes no significan automáticamente dos identidades

El nombre que aparece en Finder es la señal de identidad más débil de este experimento. `Sallyport.app`, `Sallyport Beta.app` y `Sallyport Copy.app` pueden parecer tres aplicaciones independientes para una persona, aunque dentro de sus paquetes tengan el mismo identificador de paquete y la misma identidad de firma.

Apple documenta el identificador de paquete como el identificador que macOS usa para reconocer una aplicación. Además, sus materiales sobre firma de código explican que un requisito designado permite al sistema reconocer el código como la misma aplicación entre actualizaciones. Son conceptos útiles, pero ninguno resuelve la pregunta que te interesa aquí. Que macOS reconozca una identidad no demuestra que dos procesos activos sean propietarios seguros del estado.

Empieza registrando lo que has iniciado realmente. Hazlo antes de abrir la bóveda, conectar un agente o aprobar algo.

```sh
APP_A="/Applications/Sallyport.app"
APP_B="$HOME/Desktop/Sallyport Beta.app"

for app in "$APP_A" "$APP_B"; do
  echo "=== $app ==="
  plutil -p "$app/Contents/Info.plist" | grep -E 'CFBundleIdentifier|CFBundleShortVersionString|CFBundleVersion'
  codesign -dvv "$app" 2>&1 | grep -E 'Identifier=|TeamIdentifier=|Authority='
  codesign -d -r- "$app" 2>&1 | grep 'designated =>'
done
```

Importa más la forma de la salida que el texto exacto:

```text
=== /Applications/Sallyport.app ===
"CFBundleIdentifier" => "..."
"CFBundleShortVersionString" => "..."
Identifier=...
TeamIdentifier=...
designated => identifier "..." and anchor ...
```

Conserva este resultado junto con el registro de la prueba. Si los dos paquetes intactos muestran el mismo identificador y el mismo requisito designado, considéralo dos copias de una identidad de código. Si son distintos, considéralas identidades de código separadas. No uses etiquetas como «estable» y «beta» en lugar de ninguno de esos datos.

Hay otra distinción que los equipos suelen mezclar: una identidad de código no es una identidad de almacenamiento. Es posible que se espere que dos procesos con el mismo requisito designado lean el mismo material protegido. Dos procesos con requisitos distintos pueden recibir permisos separados del sistema. Ninguno de esos resultados indica si pueden compartir de forma segura una bóveda cifrada o añadir entradas a una cadena de auditoría. Debes probar la propiedad del almacenamiento por separado.

## Un historial de auditoría dividido es peor que un registro incompleto

Un diario incompleto te indica que faltan pruebas. Un diario dividido puede contar dos historias internamente coherentes, cada una sin los registros que conserva la otra. Eso ralentiza la revisión de incidentes y puede hacer que una sesión revocada parezca válida desde el punto de vista de uno de los procesos.

Para una pasarela de acciones, el diario debe responder después a preguntas concretas:

- ¿Qué proceso del agente solicitó la acción?
- ¿Qué proceso de la pasarela la autorizó o rechazó?
- ¿Qué referencia de credencial se utilizó sin revelar el secreto?
- ¿Una persona aprobó la sesión o la llamada individual?
- ¿La revocación ocurrió antes o después de la acción?

Si los procesos A y B escriben secuencias independientes, ambas pueden verificarse por separado. Eso no basta. Verificar una cadena criptográfica demuestra que los registros de esa cadena no se han alterado sin ser detectados. Por sí sola, no demuestra que otro escritor legítimo no haya iniciado una cadena separada en otro lugar.

Por eso importa distinguir entre evidencia de manipulación y completitud. Una cadena de hashes protege la relación entre los registros presentes en esa cadena. La completitud exige una regla clara de propiedad, un único punto de adición o pruebas duraderas de cada rama aceptada y de cómo se reconcilió. Si te equivocas aquí, una persona responsable puede verificar correctamente el registro y aun así no ver la mitad de las acciones.

El problema no se limita a los registros. Un historial de auditoría dividido suele ir acompañado de una autoridad dividida:

- Un proceso cree que la bóveda está bloqueada mientras otro mantiene una sesión desbloqueada.
- Un proceso revoca una ejecución de agente mientras otro todavía la acepta.
- Un proceso registra una aprobación por llamada mientras otro no ve motivos para preguntar.
- Un proceso escribe el resultado final de la acción mientras el otro solo escribe la solicitud.

No evalúes la prueba según si ambas copias pueden completar una llamada HTTP o un comando SSH. Una llamada correcta solo demuestra que existe una ruta. La prueba se supera cuando una persona que revise el caso después puede reconstruir todo el historial de acciones sin tener que adivinar qué copia conservaba el estado que falta.

## Decide el contrato de propiedad antes de iniciar la carrera

Una prueba de instancias duplicadas sin invariantes esperadas solo produce anécdotas. Escribe primero el contrato y después intenta romperlo.

Hay tres contratos defendibles para las copias de una pasarela local.

1. **Propiedad exclusiva.** El primer proceso es propietario de la bóveda y de los diarios. Las copias posteriores se niegan a funcionar, ponen la primera aplicación en primer plano o salen con un motivo claro.
2. **Un proceso activo con transferencia.** Un lanzamiento posterior descubre al propietario y le pide que realice el trabajo solicitado. El proceso nuevo no desbloquea, aprueba ni añade entradas por su cuenta.
3. **Propiedad coordinada entre varios procesos.** Pueden funcionar varios procesos, pero todos usan un protocolo deliberado de estado compartido que conserva los cambios atómicos de la bóveda y un único orden auditable de eventos.

El primer contrato suele ser el más fácil de razonar para una aplicación de escritorio que contiene estados de gran impacto. El tercero puede ser válido, pero exige muchas más pruebas. «Ambos apuntan a la misma carpeta» no es un protocolo de coordinación.

Escribe los resultados esperados en una tabla antes de probar. Cada resultado debe poder observarse.

| Condición | Comportamiento esperado | Pruebas que se deben conservar |
| --- | --- | --- |
| Stable es propietario de una bóveda desbloqueada | El lanzamiento de Beta se rechaza, se transfiere o se coordina | Estado de la interfaz, lista de procesos, respuesta de la pasarela |
| Stable es propietario de una bóveda bloqueada | Ninguna copia puede ejecutar una acción hasta que se abra la bóveda | Registro de solicitud rechazada y estado local |
| Stable tiene una sesión de agente aprobada | Un proceso de agente nuevo necesita su propia decisión de sesión | Registros de aprobación con identidad del proceso |
| Se ha configurado la aprobación por llamada para una credencial | Cada uso solicita aprobación, sin importar qué copia lo reciba | Un registro de aprobación por cada llamada intentada |
| Una ejecución se revoca en un proceso | Ningún proceso puede continuar esa ejecución | Evento de revocación y llamada posterior rechazada |
| Ambas copias solicitan una acción inocua a la vez | El historial se mantiene completo y se verifica correctamente | Registros de actividad ordenados y verificación de auditoría |

La redacción importa. «Las copias no deberían entrar en conflicto» no se puede probar. «Un paquete copiado no puede devolver una respuesta de acción correcta después de que un propietario existente haya revocado la misma ejecución de agente» sí se puede probar.

Separa también el comportamiento del producto del comportamiento del entorno de pruebas. Tu script de shell puede impedir por accidente dos lanzamientos, pero eso no demuestra que la aplicación los impida. Tu proxy inverso puede serializar las solicitudes, pero eso no demuestra que la bóveda local gestione las solicitudes simultáneas. No dejes que el entorno de pruebas resuelva el error que querías encontrar.

## Inicia copias intactas antes de probar artefactos modificados

El experimento limpio empieza con paquetes de aplicación intactos. Copia un paquete sin cambiar los archivos que contiene, coloca las copias en ubicaciones normales separadas y registra cada ruta. Modificar primero un ejecutable o `Info.plist` suele invalidar la firma de código, lo que convierte el experimento en una prueba de validación de firma.

La nota técnica TN3127 de Apple explica por qué importa este límite: macOS utiliza requisitos designados para decidir si el código cumple una identidad establecida. Si modificas un paquete y su firma deja de validarse, cualquier rechazo puede ser correcto, pero no te dice nada sobre la propiedad duplicada entre compilaciones válidas.

Usa una pequeña hoja de lanzamiento. Debe contener solo hechos que puedas observar:

```text
Test ID: duplicate-owner-01
Build A path: /Applications/Sallyport.app
Build B path: /Users/tester/Desktop/Sallyport Beta.app
Bundle IDs: [recorded value A] / [recorded value B]
Designated requirements: [recorded value A] / [recorded value B]
Launch order: A first, B second
Vault state before launch: locked
Agent process IDs: [record after start]
Expected contract: exclusive ownership
```

Después inicia A, espera hasta que llegue a su estado inactivo y luego inicia B. Registra el resultado antes de hacer cualquier otra cosa. El rechazo debe ser lo bastante explícito para que la persona responsable sepa qué proceso existente es propietario del estado. Una salida silenciosa genera consultas de soporte y anima a repetir el intento hasta producir una carrera.

Confirma la separación real de los procesos con el sistema operativo, no con las ventanas de la aplicación. Una segunda ventana puede pertenecer al mismo proceso, mientras que un asistente en segundo plano puede crear un segundo proceso aunque solo haya un elemento visible en la barra de menús.

```sh
pgrep -alf 'Sallyport|sp mcp|sp-ssh'
ps -axo pid,ppid,start,command | grep -E 'Sallyport|sp mcp|sp-ssh' | grep -v grep
```

Trata la salida como una prueba de línea de tiempo. Guárdala inmediatamente después de cada fase de lanzamiento. Incluye los ID de los procesos principales cuando sea posible. Ayudan a explicar si la segunda acción provino de un proceso de aplicación independiente, de un intermediario iniciado por el agente o de un asistente secundario.

No uses credenciales de producción para este trabajo. Dirige las solicitudes HTTP a un punto de conexión de pruebas específico que acepte un método inocuo o a un servicio de pruebas controlado. Para SSH, usa una cuenta específica con una restricción de comandos o un equipo sin acceso a producción. Estás probando la propiedad del proceso y el comportamiento de auditoría. No necesitas un punto de conexión destructivo para saber si una segunda copia puede actuar.

## La puerta de la bóveda debe tener un único propietario visible

La puerta de la bóveda solo es un límite absoluto si todas las rutas de acción llegan a la misma puerta. Si un proceso puede permanecer desbloqueado después de que otro proceso bloquee la bóveda, se cierre o pierda el acceso, entonces «bloqueada» describe una ventana y no el estado real del sistema.

Prueba esta secuencia primero con una acción HTTP inocua:

1. Inicia la copia A con la bóveda bloqueada.
2. Conecta un proceso de agente mediante el adaptador MCP local e intenta la acción inocua. Espera que se rechace.
3. Desbloquea A mediante la interacción local normal y repite la acción. Registra el resultado correcto y su entrada de actividad.
4. Inicia la copia B mientras A sigue disponible. No desbloquees B todavía.
5. Pide al mismo proceso de agente y a un proceso de agente nuevo que intenten la misma acción mediante la ruta accesible de B, si existe.
6. Bloquea A o ciérrala y repite las solicitudes desde ambos agentes.

El resultado correcto depende del contrato de propiedad, pero el resultado inseguro se puede describir fácilmente: B ejecuta la acción porque conserva o adquiere por separado una autoridad que la persona no podía ver cuando bloqueó A.

Aquí falla una recomendación habitual: «Comparte simplemente el estado desbloqueado para que la beta no siga preguntando». Se recomienda porque la autenticación local repetida resulta molesta durante el desarrollo. Es incorrecto salvo que el estado compartido tenga un propietario definido, una duración clara y una ruta de revocación que todos los procesos observen antes de actuar. Guardar una confirmación por comodidad no merece la pena si crea un segundo estado de desbloqueo invisible.

El modelo de bóveda protegida por hardware añade un límite de prueba útil. Mientras la bóveda está bloqueada, el agente debe recibir un rechazo, no un secreto de sustitución, una solicitud preparada parcialmente ni una acción en cola que se ejecute después del desbloqueo. Una pasarela de acciones debe ejecutar por sí misma una acción aprobada y devolver su resultado. No debe entregar material de credenciales al agente mientras espera que la persona resuelva el estado local.

Registra tanto la respuesta de la acción como las pruebas del diario. Un rechazo que desaparece del historial complica innecesariamente la depuración. Un resultado correcto que aparece sin un registro previo de desbloqueo o aprobación es aún peor.

## La aprobación de sesión debe vincularse a un proceso, no a una etiqueta

La autorización por sesión existe para que una persona evalúe una ejecución concreta de un agente. Pierde su sentido cuando la autorización pasa de un proceso a otro porque ambas ejecuciones tienen un nombre parecido, se inician desde el mismo terminal o se conectan mediante el mismo paquete copiado.

El registro de aprobación debe permitir responder a estas preguntas: ¿qué ejecutable la solicitó?, ¿quién lo firmó?, ¿cuándo empezó ese proceso? y ¿cuándo terminó su autoridad? La autoridad de firma de código del proceso resulta especialmente útil porque un comando de terminal por sí solo no identifica el código que hay detrás.

Ejecuta esta prueba con dos procesos de agente deliberadamente separados. No reutilices un único proceso de shell y lo llames dos agentes.

```sh
# Terminal 1
sp mcp

# Terminal 2
sp mcp
```

Los comandos son intencionadamente sencillos. Lo importante es que procedan de procesos de agente separados en tu entorno de pruebas y que después cada uno envíe una llamada inocua. Aprueba la primera sesión y deja la segunda sin responder. Si tu configuración muestra detalles del proceso en la tarjeta de aprobación, captura la autoridad mostrada y compárala con el proceso que iniciaste.

Prueba ahora estas transiciones:

- Cierra el proceso de agente aprobado y después inicia un proceso de sustitución con el mismo comando.
- Inicia o activa una pasarela copiada mientras la primera sesión sigue aprobada.
- Revoca la primera ejecución desde el diario de sesiones.
- Pide a ambos procesos que emitan una solicitud inocua más.

La regla esperada es sencilla: una aprobación pertenece a una ejecución hasta que esa ejecución termina, y una revocación acaba con la autoridad de esa ejecución en todas partes. Un proceso de sustitución no debe heredar la aprobación solo porque parezca familiar. Una pasarela copiada no debe interpretar la misma conexión del agente como permiso para saltarse una decisión que su proceso equivalente exigiría.

No confundas la aprobación por sesión con las claves por llamada. La aprobación de sesión responde a si ese proceso de agente puede usar la pasarela durante su ejecución. Una clave por llamada responde a si cada uso de una credencial concreta necesita una nueva decisión humana. Una se refiere a la duración del autor; la otra, a la sensibilidad de la credencial. Si las mezclas en tus notas de prueba, interpretarás mal el resultado.

## La aprobación por llamada revela rutas duplicadas ocultas

Una credencial configurada para pedir aprobación en cada uso es una buena forma de detectar instancias duplicadas porque obliga al software a registrar cada acción por separado. Usa una credencial de pruebas específica o un destino SSH dedicado, activa el requisito de aprobación por llamada y envía solicitudes inocuas simultáneas mediante las dos rutas candidatas.

La prueba no consiste en «¿recibí dos solicitudes?». Dos solicitudes pueden ser correctas o pueden indicar que ambas copias crearon una autoridad independiente. La pregunta es si cada acción completada tiene una aprobación correspondiente y si alguna copia puede reutilizar la aprobación de la otra.

Crea un pequeño registro de eventos durante la prueba:

| Hora | Proceso de la pasarela | PID del agente | ID de solicitud | Decisión humana | Resultado |
| --- | --- | --- | --- | --- | --- |
| 10:03:01 | A | 4128 | req-a1 | aprobada | correcta |
| 10:03:02 | B | 4194 | req-b1 | rechazada | rechazada |
| 10:03:04 | A | 4128 | req-a2 | aprobada | correcta |

Usa los ID de solicitud generados por el punto de conexión de pruebas o por el entorno de pruebas. No dependas del orden en que aparecieron las ventanas. La planificación del escritorio puede cambiar el orden de las solicitudes visibles y una respuesta HTTP puede llegar después de una solicitud posterior.

Un resultado incorrecto suele parecer ordenado al principio: apruebas la solicitud A en la copia A y luego descubres que la copia B puede completar la solicitud B sin su propia aprobación. Eso indica que el estado de aprobación cruzó un límite sin una decisión de la persona responsable. El fallo inverso también es grave: apruebas B, pero el resultado se registra en el flujo de actividad de A sin explicar por qué actuó A.

Repite la carrera después de bloquear la bóveda, después de cerrar una copia y después de revocar la primera ejecución del agente. Esas transiciones detectan estados obsoletos en memoria. Los errores más reveladores suelen aparecer después de un cambio de estado, cuando un proceso ha actualizado su visión y el otro no.

## La verificación de auditoría necesita una prueba anterior y otra posterior

Un informe que dice «el registro parecía correcto» no es una prueba. Captura una línea base, crea un conjunto controlado de acciones, verifica después de la ejecución y compara los registros esperados con los observados.

El diseño de auditoría descrito para esta pasarela tiene una ventaja: el mismo registro cifrado, encadenado mediante hashes y que no puede leer quien escribe se proyecta tanto en el diario de sesiones como en el diario de actividad. Por tanto, los diarios deberían ser dos vistas de un único historial, no dos registros mantenidos de forma independiente que solo se parecen.

Empieza con un periodo de pruebas vacío o claramente delimitado. Anota la hora de inicio y genera después una secuencia conocida: rechazo con la bóveda bloqueada, acción con sesión aprobada, acción aprobada por llamada, rechazo por llamada, revocación de sesión y acción posterior a la revocación rechazada. Mantén la secuencia lo bastante corta para poder explicar cada evento.

Después ejecuta el verificador sin conexión:

```sh
sp audit verify
```

Conserva toda la salida del comando, incluido un código de salida distinto de cero si falla. El verificador no debería necesitar acceso a la bóveda para comprobar la cadena de texto cifrado. Esa propiedad permite a un investigador verificar la integridad sin desbloquear primero el almacén de credenciales, justo lo que necesitas cuando una prueba de procesos duplicados ha salido mal.

La verificación por sí sola es solo el primer control. Compara tres vistas:

1. Tu registro de pruebas externo, con cada ID de solicitud y resultado esperado.
2. El diario de sesiones, con las aprobaciones, salidas de ejecuciones y revocaciones.
3. El diario de actividad, con cada intento de acción y su resultado.

Cada evento del registro debe corresponder a la vista adecuada del diario. Cada evento del diario dentro del periodo delimitado debe corresponder a algo que hayas generado intencionadamente. Investiga los registros adicionales, aunque la cadena se verifique. Una acción correcta inesperada puede ser un reintento, una solicitud en cola o una prueba de que un segundo proceso seguía activo después de que creyeras que había terminado.

Si se verifican dos cadenas independientes, informa de ello como un fallo de completitud del historial, salvo que el diseño documentado para varios procesos registre una relación principal y una combinación determinista. No lo soluciones concatenando los registros exportados después de la prueba. Una combinación manual produce un informe, no un historial de auditoría.

## El comportamiento de recuperación determina si un error de duplicación se convierte en un incidente

Debes esperar intentos de propiedad interrumpidos. Alguien puede forzar el cierre de las compilaciones beta. Los portátiles pueden entrar en reposo. Un entorno de pruebas puede terminar un proceso entre una aprobación y el resultado de una acción. La aplicación debe gestionar estos casos sin permitir que un propietario abandonado bloquee el trabajo para siempre ni que un propietario sustituto suponga que no ocurrió ninguna acción.

Prueba al menos cuatro puntos de interrupción:

- Termina la copia A después de recibir una solicitud, pero antes de obtener la aprobación.
- Termina la copia A después de la aprobación, pero antes de que termine la acción externa.
- Termina la copia A después de completar la acción, pero antes de que el resultado sea visible para el agente.
- Inicia la copia B inmediatamente después de cada interrupción.

Para cada caso, escribe la regla de recuperación segura antes de ejecutarlo. Una regla razonable podría indicar que B se niega a funcionar hasta establecer que A ha terminado y recuperar el estado persistente. Otra podría indicar que B solo reanuda la actividad a partir de registros de auditoría confirmados y marca como desconocida cualquier acción incierta en lugar de declararla correcta o fallida. La regla correcta depende de la implementación, pero adivinar nunca es aceptable.

No ocultes la incertidumbre. Los sistemas externos pueden completar una solicitud HTTP o un comando SSH justo cuando muere el proceso local. Si la pasarela no puede demostrar si la operación terminó, debe conservar esa ambigüedad en las pruebas. Repetir una escritura desconocida puede crear una segunda acción real. Fingir que la primera nunca ocurrió crea una historia de auditoría falsa.

Aquí también hay que examinar la revocación inmediata. Revoca una ejecución de agente, termina el propietario actual justo después, inicia una copia e intenta la misma acción desde ese agente. Si el proceso sustituto interpreta la ausencia del estado en memoria como una sesión nueva, la revocación no era suficientemente persistente. Una revocación que solo funciona mientras un proceso permanece activo no es una revocación en la que puedas confiar durante un fallo.

## Mantén aisladas las pruebas estable y beta salvo que compartir sea deliberado

Las compilaciones estable y beta tientan a los equipos a convivir sin planificación porque ambas resultan útiles. Stable contiene trabajo real. Beta necesita una presión realista. Enfrentarlas a la misma bóveda y a los mismos diarios sin un contrato de compatibilidad deliberado combina los riesgos de producción con la observabilidad de un experimento.

Usa una de estas dos disposiciones. La opción más segura da a beta una bóveda de pruebas, credenciales de pruebas, agentes de pruebas y un conjunto de pruebas separado. Así se prueba el comportamiento de beta sin permitir que un proceso experimental forme parte del registro de acciones de producción.

La opción más difícil hace que stable y beta compartan el estado de forma intencionada. Elígela solo cuando la continuidad entre versiones sea precisamente el objeto de la prueba. En ese caso, prueba la diferencia de versiones en ambos sentidos: stable es propietaria del estado y después se inicia beta, beta es propietaria del estado y después se inicia stable, un proceso se actualiza mientras otro permanece abierto y un proceso vuelve a una versión anterior después de que el otro haya escrito un historial nuevo. Registra si cada transición se rechaza, se transfiere o se coordina.

Para Sallyport, la secuencia fija de decisiones ofrece un marco de aceptación útil: la puerta de la bóveda sigue siendo absoluta, un proceso de agente nuevo recibe por defecto su propia autorización de sesión y las credenciales marcadas para aprobación por llamada siguen preguntando en cada uso. La implementación puede elegir la propiedad exclusiva o una transferencia segura, pero no puede debilitar silenciosamente esos controles para que dos paquetes parezcan cómodos de usar.

Termina la prueba con una declaración clara que otro ingeniero pueda cuestionar: qué proceso era propietario de la bóveda, qué ocurrió cuando apareció otra copia, si alguna acción cruzó un límite de aprobación o revocación y si una única verificación sin conexión cubrió todos los eventos generados. Si la respuesta depende de qué ventana estabas mirando, repite la prueba. Las pruebas todavía no son suficientemente buenas.
