# macOS Keychain para herramientas de programación autónomas

macOS Keychain protege bien una credencial mientras nadie la usa. Eso no lo convierte en un límite de seguridad completo para una herramienta de programación autónoma. Cuando un proceso puede recuperar un token de API o una clave privada, normalmente también puede copiarlo, imprimirlo, enviarlo a otro sitio o seguir usándolo cuando el operador cree que la tarea ya terminó.

Esta diferencia pesa más con un agente que con una aplicación de escritorio convencional. Una persona elige un comando de menú y ve el resultado inmediato. Un agente interpreta texto, invoca herramientas, sigue las salidas y puede continuar durante horas. El almacenamiento de Keychain responde: «¿Quién puede leer este secreto?». La ejecución intermediada responde algo más difícil: «¿Puede este proceso concreto realizar ahora esta acción concreta sin recibir nunca el secreto?». La autenticación de sesión PAM responde otra pregunta relacionada, acerca de quién abrió una sesión. Confundir estos tres controles deja huecos difíciles de ver en una demostración y dolorosos de reconstruir tras un incidente.

## Keychain protege credenciales almacenadas, no cada uso

Keychain cifra el material secreto almacenado y media el acceso mediante los servicios de seguridad de macOS. Es un lugar mucho mejor para un token que un archivo `.env`, el historial del shell, un repositorio o la configuración del agente. Un elemento puede tener controles de acceso y el sistema puede exigir la presencia del usuario antes de entregarlo. Son defensas reales ante el robo casual de archivos y las lecturas no autorizadas.

El límite termina al revelar el dato. Si una herramienta llama a un auxiliar que devuelve un token como texto, el token ya existe en el auxiliar, en la ruta IPC y en el proceso receptor. Puede aparecer en un mensaje de depuración, un objeto de excepción, una transcripción, un informe de fallo, la memoria de intercambio, un entorno copiado, un proceso hijo o una petición saliente maliciosa. Keychain no puede recuperar esos bytes. Bloquearlo después tampoco invalida un token portador ya copiado en memoria.

La documentación Keychain Services de Apple habla de guardar y recuperar contraseñas, claves y certificados. La formulación importa: recuperar es un resultado admitido. A veces se describe Keychain como si convirtiera cada secreto en una clave de firma no exportable. No es así. Algunas claves criptográficas pueden crearse con controles de acceso y usarse mediante las API de seguridad sin exportar su parte privada, pero un token de API común debe acabar en alguna cabecera HTTP. La pregunta de seguridad es qué componente de confianza construye y envía esa petición.

Puedes mostrar el problema con una cuenta de prueba en vez de un token de producción. Guarda un valor desechable, deja que lo recupere la misma ruta que usa el agente y examina qué puede hacer el proceso con los bytes devueltos:

```sh
security find-generic-password -a agent-test -s example-api -w
```

La salida tiene el secreto seguido de un salto de línea:

```text
test_token_7f3a...
```

Si el agente o un shell bajo su control ejecutan la orden con éxito, la capa de almacenamiento ya tomó su decisión. Ocultar el texto en el terminal solo cambia lo que ve una persona. No impide que el proceso redirija la salida, la codifique o la incluya en una petición.

## Un lector autorizado puede convertirse en exportador

El principal peligroso no suele ser un atacante desconocido. Es la herramienta a la que permitiste trabajar. Un proceso autónomo necesita acceso amplio a archivos, herramientas de compilación, gestores de paquetes y clientes de red para resultar útil. Si también puede leer credenciales, una inyección de instrucciones, una dependencia comprometida o una orden equivocada puede convertir el acceso normal en una exportación.

Los requisitos de firma de código pueden limitar qué aplicación accede a un elemento de Keychain. Ayudan cuando la amenaza es otro proceso sin firma o con una firma distinta. Ayudan mucho menos si la aplicación aprobada admite extensiones, inicia shells, carga complementos, acepta instrucciones no fiables del repositorio o expone un protocolo de herramientas. El binario firmado puede ser justo el proceso que ejecuta el comportamiento influido por el atacante.

También hay un problema de delegación. Un auxiliar de escritorio aprobado puede leer un token y pasarlo al agente por la entrada estándar. Keychain ve al auxiliar aprobado, no al consumidor final. Si el auxiliar pone el token en una variable de entorno, todos los hijos que hereden ese entorno pueden recibirlo. La decisión de acceso original no expresa qué operación posterior se pretendía realizar.

Una revisión útil sigue el secreto como dato, no como una caja de un diagrama. Plantea cuatro preguntas concretas:

- ¿Qué proceso recibe primero el texto sin cifrar?
- ¿Puede obtener una copia algún hijo, complemento, comando de shell o transcripción?
- ¿Puede el receptor elegir destino, método HTTP, ruta o host SSH?
- ¿Qué hecho termina su autoridad y borra o no el material ya revelado?

Muchos equipos responden la primera y omiten las otras. El diseño parece protegido en reposo, pero se comporta como una credencial duradera en texto claro en cuanto arranca el agente.

## PAM demuestra un inicio de sesión, no la intención del agente

Los módulos PAM permiten que un servicio aplique una política de autenticación en el límite de inicio de sesión. En macOS, servicios como `sudo`, `login` y las rutas de acceso remoto usan configuraciones PAM con nombre. Una pila PAM puede autenticar al usuario, comprobar la cuenta, establecer credenciales y abrir o cerrar una sesión. Sirve para decidir si alguien puede iniciar una sesión privilegiada del sistema operativo.

PAM no suele mediar cada llamada HTTP de un agente que ya está en marcha. Tras autenticarse, el proceso actúa con la identidad y las capacidades del sistema operativo que recibió. Si puede leer un token, PAM no vincula el gesto anterior de Touch ID o la contraseña con una única petición posterior. Tampoco sabe que `POST /releases` es más sensible que `GET /status`, salvo que otro componente implemente esa distinción de aplicación.

La palabra «sesión» confunde porque nombra vidas distintas. Una sesión PAM puede envolver un inicio de sesión o una actividad de `sudo`. Una sesión de terminal puede persistir en una ventana. Una sesión de agente puede significar una ejecución, una conversación o una tarea reanudada. Una sesión de API puede durar lo mismo que un token portador. Terminar una no termina necesariamente las demás.

El fallo puede ocurrir así: un operador se autentica para abrir un shell, inicia el agente y aprueba el acceso a Keychain. El agente lee un token de despliegue. Horas después, el operador cierra la conversación visible, pero sigue vivo un hijo que conserva el token en el entorno. PAM autenticó correctamente la sesión original y Keychain autorizó bien la lectura. Ninguno impidió el uso posterior porque ninguno controlaba la vida de la acción.

Usa PAM cuando la decisión sea si una cuenta del sistema puede establecer una sesión. No presentes esa autenticación como prueba de una intención humana reciente en cada acción interior. Eso exige a PAM una semántica que no tiene.

## Autenticación y autorización responden preguntas distintas

La autenticación establece quién es un principal, o al menos qué credencial presentó. La autorización decide qué puede hacer en un contexto. El almacenamiento conserva material hasta una lectura u operación criptográfica autorizada. Los controles se apoyan, pero no se sustituyen.

Con herramientas autónomas hay que nombrar al principal con precisión. «El usuario» es demasiado vago. El operador, el ejecutable firmado del agente, un nuevo proceso, un servidor MCP, un shell hijo y la cuenta remota de API son principales distintos. Si una aprobación abarca a todos, el diseño debe decirlo.

La unidad práctica para el trabajo rutinario suele ser la ejecución del proceso. El operador autoriza un proceso reconocido hasta que sale y toma otra decisión para una ejecución nueva. Así un reinicio silencioso no hereda el permiso anterior. Para una credencial muy sensible, la autorización debe ser aún más estrecha y pedir aprobación en cada uso.

La aprobación por llamada tampoco es segura por sí sola. Un diálogo que solo diga «¿Permitir acceso a la red?» no permite juzgar casi nada. Debe identificar el proceso y mostrar los detalles que cambian el riesgo, como el alias de credencial, método y destino HTTP, o usuario y host SSH. También debe evitar la fatiga. Si una lectura inocua interrumpe igual que una escritura de producción, la gente aprobará ambas sin mirar.

La identidad del proceso merece más que una ruta o un nombre visible, pues ambos se copian. En macOS, un requisito de firma puede vincular la decisión con la autoridad firmante y el requisito designado, mientras el identificador distingue una instancia de otra. Muestra el hecho de firma que el sistema verificó, no un nombre amable que otro binario pueda imitar.

La firma no demuestra buen comportamiento. Establece procedencia según su modelo, no intención. Un agente bien firmado puede obedecer instrucciones hostiles de un repositorio, y un host firmado puede cargar contenido controlado por un atacante. Aprobar la sesión debe significar «este proceso identificado puede pedir acciones hasta que salga», no «todas las solicitudes de este programa son seguras».

Define antes cómo se relacionan los descendientes. Confiar automáticamente en todos crea árboles difíciles de revocar y permite que un shell general herede demasiada autoridad. Pedir aprobación para cada auxiliar breve vuelve inútil la automatización. Un diseño limpio mantiene la autorización en la conexión del intermediario que posee el agente aprobado. Los hijos no heredan credenciales y solo alcanzan acciones protegidas por un canal mediado que se revoca con la sesión padre.

La salida del proceso es un final automático útil, pero el operador también necesita revocación inmediata. El intermediario debe rechazar acciones en cola y futuras, invalidar la sesión y registrar la decisión. Poder detener una petición ya transmitida depende del protocolo y del servicio remoto; la interfaz no debe fingir que revocar deshace trabajo completo.

Prueba también los reinicios. Algunos clientes se actualizan, reconectan tras un fallo auxiliar o reanudan una conversación en otro proceso. El código cómodo suele tratarlo como una misma sesión lógica. El límite de seguridad debe verlo como proceso nuevo salvo que un protocolo de traspaso deliberado y autenticado demuestre continuidad. Un identificador de conversación aportado por el agente no es prueba porque puede copiarlo.

El resultado es una escala de decisiones, no un permiso enorme:

1. ¿Está desbloqueado y disponible el almacén?
2. ¿Puede actuar este nuevo proceso durante su vida?
3. ¿Exige esta credencial una decisión en cada uso?
4. ¿Limitará y registrará la operación un ejecutor de confianza?

Las tres primeras gobiernan autoridad. La cuarta gobierna ejecución y evidencia. Fundirlas en una opción vaga de «agente fiable» complica la revocación y la investigación.

## La ejecución intermediada mantiene la credencial fuera del agente

Un intermediario cambia «dame el token» por «realiza esta acción definida». El agente aporta parámetros no secretos. El intermediario comprueba la autoridad, recupera la credencial dentro de su límite, la inyecta en el protocolo, ejecuta la operación y devuelve solo el resultado necesario.

Para una API HTTP, la petición podría ser:

```json
{
  "credential": "staging-release-api",
  "method": "POST",
  "url": "https://api.example.invalid/v1/releases",
  "headers": {"content-type": "application/json"},
  "body": {"commit": "8a31c2e", "channel": "candidate"}
}
```

Tras autorizar, el intermediario añade la cabecera bearer, basic o personalizada. La respuesta visible conserva estado, algunas cabeceras y cuerpo, pero omite la credencial:

```json
{
  "status": 201,
  "headers": {"content-type": "application/json"},
  "body": {"release_id": "rel_1042", "state": "queued"}
}
```

Es un límite más claro que la censura. Censurar supone que el secreto entró en una ruta no fiable e intenta ocultarlo después. La intermediación evita entregarlo. También hace efectiva la revocación: cuando se deniega la siguiente petición, el agente no conserva un token con el que eludirla.

Aun así hay que cuidar las salidas. Una API puede reflejar cabeceras, un comando SSH imprimir el entorno y un cliente detallado incluir autenticación en un error. Filtra ubicaciones conocidas antes de devolver resultados, limita el diagnóstico y prueba los fallos con el mismo rigor. «El agente nunca recibe credenciales» debe cubrir errores y registros.

SSH sigue la misma idea, con un ejecutor que entienda el protocolo. El agente pide ejecutar un comando sobre una identidad de host nombrada. El ejecutor usa la clave privada internamente, verifica el host, ejecuta y devuelve stdout, stderr y estado. Entregar un archivo temporal con la clave sigue siendo revelarla aunque luego se borre.

## Un intermediario no evita acciones autorizadas dañinas

Ocultar el token elimina muchos fallos, pero no vuelve correcta la acción solicitada. Un `DELETE` autorizado puede borrar datos sin filtrar credenciales. Un comando SSH puede dañar un host con la clave perfectamente protegida. El intermediario controla cómo se ejerce la autoridad, pero no deduce intención empresarial de la sintaxis.

El destino importa. Si el agente elige cualquier URL y el intermediario añade la credencial a ciegas, puede enviarla a un host atacante. Un ejecutor HTTP sensato vincula cada credencial con destinos apropiados o garantiza que solo inyecta autenticación donde corresponde. Los redireccionamientos requieren el mismo cuidado. En SSH, comprobar la identidad del host debe formar parte de la ejecución, no ser una opción del agente.

Los datos de respuesta siguen expuestos. Una credencial segura puede autorizar registros de clientes, configuración de despliegue u otro secreto. El agente solo necesita la respuesta mínima, pero una API genérica dificulta imponerlo. La intermediación reduce la exposición de credenciales; no evita por sí sola la pérdida de datos.

La inyección de instrucciones tampoco desaparece. El texto del repositorio, comentarios, salidas de compilación y documentación recuperada pueden inducir una acción válida pero no deseada. La aprobación humana ayuda si la tarjeta muestra detalles suficientes y la persona lee. Los sistemas de alto riesgo pueden necesitar credenciales remotas más estrechas, ámbitos en la API, ramas protegidas, entornos de ensayo, listas de comandos o un motor de flujo separado.

El consejo popular «pon todos los secretos en Keychain y exige Touch ID» mezcla una buena decisión de almacenamiento con una decisión de uso demasiado amplia. Touch ID prueba presencia al liberar material, pero no evita que el receptor aprobado lo copie o use mal. Pide biometría en el límite de la acción cuando lo merezca y no entregues el token al agente.

## La evidencia de auditoría debe describir acciones

Un registro útil dice quién pidió la acción, qué instancia lo hizo, qué alias eligió, qué destino y operación usó, qué autorización ocurrió, cuándo se ejecutó y cómo acabó. No contiene la credencial. Una transcripción de consola rara vez basta: mezcla texto del modelo, mensajes de herramientas, órdenes y salidas truncadas sin un modelo estable.

Separa eventos de sesión y llamadas, conservando su relación. El diario de sesiones muestra cuándo apareció un proceso, cómo se identificó, quién lo aprobó, si se revocó y cuándo terminó. El de actividad muestra cada operación HTTP o SSH y su resultado. Así se responde tanto «¿se autorizó esta ejecución?» como «¿qué hizo?».

Un registro cifrado protege la confidencialidad en reposo, pero no prueba que nadie borró o reordenó entradas. Una cadena de hashes enlaza cada entrada con la anterior y permite detectar cambios, ausencias u orden alterado en el texto cifrado. No prueba que se registraran todos los hechos ni impide borrar todo el almacén. Aporta evidencia sobre la integridad de los registros restantes.

La verificación debe producir un resultado sencillo y automatizable. Sallyport proyecta sus diarios Sessions y Activity desde un único registro cifrado, encadenado por hash y sin lectura de vuelta; el verificador trabaja sin conexión sobre el texto cifrado y sin clave de descifrado:

```sh
sp audit verify
```

El éxito debe informar de la cadena comprobada y devolver cero; un enlace roto debe señalar fallo y devolver otro estado. Incluye la prueba en la recopilación de incidentes y en las copias de seguridad. Un icono verde mostrado por la misma aplicación mutable no es evidencia independiente.

Los registros necesitan un contrato de censura. Guarda alias, no valores. Decide si cuerpos y salidas se guardan, truncan, resumen con hash o excluyen. Prueba peticiones mal formadas y errores de transporte: los fallos suelen recoger más contexto bruto.

## Evalúa toda la ruta con pruebas adversarias

Una afirmación arquitectónica solo cobra sentido cuando una prueba intenta romperla. Crea una credencial y un endpoint desechables, y usa exactamente los binarios y límites del trabajo normal. Una captura de un diálogo no demuestra qué llega a memoria o sobrevive en descendientes.

Un plan compacto cubre los límites principales:

1. Inicia un agente nuevo y confirma que no hereda la aprobación anterior. Anota la identidad mostrada.
2. Pide una lectura de bajo riesgo y después una escritura protegida por llamada. Comprueba que la tarjeta distingue destino y operación.
3. Busca el secreto y sus codificaciones en stdout, stderr, entorno, transcripciones, cargas de herramientas e informes de fallo visibles al agente.
4. Revoca la sesión y reintenta desde el padre y un hijo existente. Ambos deben fallar antes de abrir red o SSH.
5. Modifica, elimina y reordena copias del registro. La verificación sin conexión debe fallar en cada cadena dañada.

Añade dos abusos omitidos con frecuencia. Haz que el endpoint redirija a otro origen y comprueba que la autenticación no lo sigue. Haz que refleje todas las cabeceras y falle con un error detallado; el intermediario debe retirar la credencial antes de responder al agente.

Para SSH, prueba una clave de host desconocida, una cambiada, una solicitud interactiva de contraseña y una orden que imprima el entorno. Confirma quién toma cada decisión. Un auxiliar sin estado no debe crear otro caché ni heredar más entorno del necesario.

Los criterios deben ser observables. «Secreto protegido» no se puede probar. «La secuencia y su base64 nunca aparecen en archivos, entorno, respuestas ni registros visibles» sí. «La revocación funciona» es vago. «Toda llamada del proceso revocado y sus hijos falla antes de abrir un socket» se puede medir.

## Elige controles según la autoridad del agente

Keychain puede bastar cuando una aplicación convencional recupera una credencial de bajo impacto, su ruta es estrecha, el usuario inicia cada operación y el modelo acepta revelársela. Sigue siendo una buena capa dentro de un diseño más fuerte. No conviene sustituir un almacén cifrado maduro por un archivo casero.

La respuesta cambia si la herramienta elige operaciones, ejecuta código no fiable, crea hijos arbitrarios o trabaja sin atención continua. Dale capacidades de acción, no bytes secretos. Autentica cada proceso nuevo, reserva confirmación por llamada para claves sensibles, vincula credenciales con protocolos y destinos, y haz que revocar corte de inmediato el uso futuro.

Antes de elegir un diseño, escribe la afirmación más fuerte que necesitas sostener. El cifrado en disco exige almacenamiento; limitar las operaciones a este proceso firmado exige identidad y sesión; llamar a una API sin obtener su token exige un intermediario; detectar cambios en los registros restantes exige pruebas de integridad. Cada afirmación tiene una prueba y un responsable distintos.

Anota también el riesgo residual. Un intermediario no deshace una escritura remota, una sesión no retira datos ya devueltos, la firma no demuestra instrucciones benignas y una cadena de hashes no prueba que se conservara todo el almacén. Así una revisión no atribuye a un control trabajos que nunca hace.

No hace falta migrar todas las integraciones a la vez. Empieza por tokens de despliegue, claves de infraestructura e identidades SSH con acceso compartido. Sustituye su interfaz de lectura por acciones pequeñas, elimina variables y auxiliares antiguos, y conserva una credencial de prueba hasta demostrar que las rutas viejas fallan.

Asigna también responsables para desbloquear, aprobar procesos, exigir confirmación por uso, revocar, rotar y verificar la cadena. Ante un agente extraño, revoca primero la sesión, conserva y verifica la auditoría y rota cualquier credencial que pudiera haber llegado a su memoria.

Sallyport aplica este modelo en macOS: los agentes conectan mediante su servidor `sp mcp` para acciones HTTP y SSH, mientras las claves permanecen en su bóveda cifrada dentro del proceso. Su escala fija tiene una puerta absoluta de bóveda, autorización predeterminada para cada proceso nuevo y aprobación opcional en cada uso de una clave elegida.

No confundas esto con un motor general de políticas ni con un proxy de interceptación. Un intermediario estrecho puede hacer afirmaciones fuertes sobre custodia porque posee rutas definidas. Lo que quede fuera necesita otros controles, y los servicios remotos deben aplicar ámbitos, separar entornos y revocar cuentas.

La pregunta decisiva es concreta: después de aprobar, ¿puede el proceso imprimir, copiar o reutilizar la credencial por su cuenta? Si puede, Keychain protegió el almacenamiento, pero no contuvo el uso. Conserva Keychain para lo que hace bien y mueve el límite de autoridad al ejecutor que realiza y registra la acción.
