# Secure Enclave y Touch ID para los secretos de los agentes de IA

Un agente de programación basado en IA debería poder solicitar una acción sin recibir nunca la credencial que la autoriza. Esa es la propiedad de seguridad en torno a la que merece la pena diseñar. Secure Enclave y Touch ID pueden hacer cumplir parte de esa propiedad en un Mac, pero solo cuando el secreto permanece detrás de un límite que el agente no puede leer.

Un aviso del Mac que dice que un agente puede acceder a «GitHub» no es un diseño de seguridad. Un token `ghp_...` copiado, una variable `AWS_SESSION_TOKEN` exportada o una clave privada SSH pegada en una llamada a una herramienta dan al agente un poder duradero que ningún aviso posterior de Touch ID puede retirar. La dificultad no está en cifrar la cadena. Está en negarse a entregársela a un software que puede reenviar texto arbitrario.

He visto este error de varias formas: un token guardado en `.env`, un asistente de credenciales que imprime una contraseña, una clave privada montada en un contenedor y después un agente al que se le indica que «use las credenciales disponibles». Cada decisión parece temporal. Todas convierten el secreto en parte del conjunto de trabajo del agente.

## Secure Enclave no hace seguro entregar un token portador

Secure Enclave puede proteger operaciones criptográficas, pero no puede hacer que un token portador deje de ser peligroso después de que un proceso lo lea. Esa diferencia determina si Touch ID protege un secreto de desarrollador o solo añade un aviso ceremonial antes de la filtración.

Apple describe Secure Enclave como un hardware aislado capaz de crear y usar claves privadas sin exponer su texto plano al procesador principal. Su restricción documentada importa: las claves privadas de Secure Enclave se generan allí, no pueden importar claves privadas existentes en texto plano y admiten operaciones concretas de firma y acuerdo de claves P-256. Un token de API no es una de esas claves privadas. Normalmente es una cadena opaca que un servicio remoto acepta de cualquiera que la presente. La documentación de Apple para desarrolladores, «Protecting keys with the Secure Enclave», deja claros tanto el aislamiento como sus límites.

Eso deja tres objetos diferentes que solemos mezclar:

- Una clave privada de Secure Enclave es material de clave no exportable que se usa para operaciones criptográficas limitadas.
- Un elemento de Keychain son datos de aplicación cifrados cuyo acceso puede restringir macOS.
- Una credencial portadora es un valor copiable que concede acceso allí donde un servicio lo acepta.

Tratarlos como sinónimos produce diseños deficientes. Una clave de Secure Enclave puede firmar un desafío sin revelarse. Un elemento de Keychain puede exigir la presencia del usuario antes de que el sistema devuelva sus datos. Un token portador se convierte en un secreto normal en el momento en que un proceso recibe sus bytes.

Por eso «guardamos el token en Keychain» no es una respuesta completa para un agente. Responde a la pregunta de cómo se almacena en reposo. No responde a quién puede solicitar el token, qué proceso lo recibe, si ese proceso puede pasarlo a un proceso hijo ni si puede escribirlo en stdout.

Las indicaciones de Apple sobre Keychain dejan claro el límite. Keychain Services puede exigir autenticación antes de devolver un elemento, y Secure Enclave solo proporciona un resultado de aprobado o rechazado para la comprobación biométrica. Ni la app ni el sistema operativo reciben los datos de la huella. Es una protección excelente para la plantilla biométrica. No dice nada sobre lo que hace después una app autorizada con los bytes de la contraseña que recibe.

No le pidas a Touch ID que resuelva un problema después de haber entregado el secreto al agente.

Un modelo mejor tiene dos capas. El proceso de la bóveda puede recuperar o usar una credencial después de que el usuario supere su control. El agente puede solicitar una acción indicando una credencial y un destino, pero no puede leer ni sustituir la credencial ni pedir a otra herramienta que la imprima. El proceso de la bóveda realiza la solicitud HTTP o la autenticación SSH y devuelve un resultado deliberadamente limitado.

Es una interfaz más estrecha. Ese es precisamente el objetivo.

## El límite debe estar antes de stdout y de las variables de entorno

Los secretos de desarrollo escapan por los conductos habituales mucho antes de que un atacante necesite derrotar el cifrado. Las variables de entorno, la herencia a procesos hijos, el trazado del shell, los registros de depuración, los informes de fallos, las respuestas de las herramientas y las copias de la salida del terminal convierten un secreto protegido en uno portátil.

Lo digo sin rodeos porque el modo de fallo es muy predecible: si un agente puede ejecutar `printenv`, leer `.env`, invocar un asistente de credenciales u obtener una clave de API como resultado de una herramienta MCP, tiene la credencial. Que el almacenamiento original usara Keychain, un gestor de contraseñas o un archivo cifrado ya no cambia la amenaza.

Considera este flujo habitual:

```text
Agent -> runs a shell command -> credential helper reads Keychain
      -> helper prints token -> shell captures stdout
      -> agent receives token -> token appears in context or logs
```

El aviso de Keychain puede haber funcionado exactamente como estaba previsto. El sistema autenticó al usuario del Mac. Después, el asistente convirtió un elemento protegido en texto, y el agente lo recibió por el mismo canal que usa para los errores del compilador y la salida de las pruebas.

Ese es el momento en que falla el diseño.

Una ruta segura para las credenciales tiene un aspecto muy diferente:

```text
Agent -> requests "POST api.example.com/releases" using credential "release-bot"
      -> gateway asks for authorization if required
      -> gateway obtains or uses credential internally
      -> gateway sends HTTPS request with Authorization header
      -> agent receives status, selected headers, and response body
```

El agente recibe el resultado de la acción autenticada, no la cabecera de autorización. Parece una pequeña decisión de API. Es la línea que separa la ejecución delegada de la distribución de secretos.

La misma regla se aplica a SSH. No entregues a un agente una ruta a `~/.ssh/id_ed25519`, un `SSH_AUTH_SOCK` capaz de firmar desafíos arbitrarios sin un límite visible ni un comando que pueda extraer una clave de un almacenamiento seguro. Deja que un asistente con un alcance limitado establezca la conexión SSH y ejecute el comando solicitado. Devuelve stdout, stderr, el estado de salida y la información de identidad del host. Mantén la clave privada fuera del árbol de procesos del agente.

Tiene un coste. Algunas herramientas de desarrollo suponen que pueden leer las credenciales directamente, y un enfoque basado en una puerta de enlace exige adaptadores, APIs de herramientas más estrechas y cierta fricción en flujos de autenticación poco habituales. Prefiero asumir ese coste de ingeniería una vez antes que rotar un token de producción después de que aparezca en la transcripción de un agente.

No confundas un disco cifrado con una ruta de ejecución controlada.

## Touch ID demuestra presencia, no intención

Touch ID puede demostrar que una persona aprobó un control en un momento concreto. No puede demostrar que entendiera el siguiente comando del agente, que el comando coincida con la intención del repositorio o que el destino sea seguro.

El framework LocalAuthentication de Apple expone deliberadamente un resultado limitado a la app: el framework coordina la operación con Secure Enclave y devuelve éxito o fallo. La app que llama proporciona el texto explicativo y elige la política de autenticación. Esa separación es correcta. El sistema no debe fingir que puede interpretar la intención de una aplicación a partir de un evento biométrico.

Para el trabajo con agentes, trata Touch ID como un control sobre una capacidad, no como la aprobación de un texto. Una tarjeta que diga «Permitir que este agente use credenciales de producción» ofrece muy poca información a la persona. Una tarjeta que indique el proceso firmado, el host de destino, la etiqueta de la credencial y si la aprobación dura una llamada o una ejecución sí permite tomar una decisión.

Prefiero tres momentos de autorización distintos:

1. Desbloquear la bóveda. Mientras la bóveda esté bloqueada, cualquier acción respaldada por secretos debe fallar. No debe existir una alternativa que permita «usar una opción sin protección».
2. Aprobar un proceso nuevo del agente durante la ejecución. La aprobación debe identificar la autoridad de firma del código, no solo un nombre de proceso mutable como `node` o `python`.
3. Exigir la presencia del usuario para cada uso de credenciales con un alcance irreversible, como un token de despliegue en producción, un rol de propietario de la nube o una clave SSH capaz de modificar toda una flota.

La aprobación por sesión es el valor predeterminado correcto para los agentes de programación. Un proceso nuevo es un límite significativo: un lanzamiento nuevo puede usar otro binario, otro espacio de trabajo, otras variables de entorno heredadas u otra configuración MCP. La aprobación permanente silencia las acciones posteriores precisamente cuando resulta más difícil inspeccionar su procedencia.

La aprobación por llamada debe seguir siendo poco frecuente, pero debe ser estricta cuando la credencial pueda causar un daño amplio. Un token que solo abre un gestor de incidencias de solo lectura no necesita una huella para cada `GET`. Una credencial SSH que pueda ejecutar `kubectl apply` en producción sí. Esto genera interrupciones, y esas interrupciones son intencionadas.

La alternativa habitual es un archivo de políticas grande: aprobar comandos que coincidan con esta expresión regular, permitir dominios de esta lista, rechazar argumentos que contengan ciertas palabras. Parece escalable porque sustituye los avisos por automatización. También crea un segundo lenguaje de programación que debe modelar las comillas del shell, las redirecciones, los envoltorios, los enlaces simbólicos, `curl --config`, los cuerpos codificados, la expansión de comandos remotos y cada herramienta nueva que instale un agente.

No pondría autoridad de producción detrás de una gramática que nadie audita después del viernes.

Usa en su lugar una pequeña secuencia de decisiones: bloqueada o desbloqueada, ejecución aprobada o no, esta credencial necesita una aprobación nueva o no. Estos controles tienen un significado visible cuando comienza la revisión de un incidente.

## Los controles de acceso de Keychain tienen bordes peligrosos

Un elemento biométrico de Keychain solo resulta útil cuando eliges deliberadamente la restricción de acceso, entiendes su comportamiento alternativo y evitas que el proceso autorizado se convierta en un dispensador de secretos.

Apple documenta `SecAccessControlCreateWithFlags` para asociar requisitos de accesibilidad y autorización a un elemento de Keychain. Para un secreto local de desarrollo que no deba migrar mediante una copia de seguridad o iCloud Keychain, `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` suele ser la clase de almacenamiento sensata. Requiere un código del dispositivo y hace que el elemento deje de estar disponible si se elimina ese código; el sufijo `ThisDeviceOnly` también impide transferirlo a otro dispositivo.

Para un secreto que deba exigir una comprobación biométrica, usa una bandera de control de acceso en lugar de intentar añadir un aviso alrededor de una consulta normal a Keychain. Este fragmento simplificado de Swift guarda una contraseña genérica que solo puede liberar el conjunto biométrico inscrito actualmente:

```swift
import Security

let access = SecAccessControlCreateWithFlags(
    kCFAllocatorDefault,
    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
    .biometryCurrentSet,
    nil
)!

let item: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: "com.example.agent-gateway",
    kSecAttrAccount: "release-bot",
    kSecAttrAccessControl: access,
    kSecValueData: Data(token.utf8)
]

let status = SecItemAdd(item as CFDictionary, nil)
precondition(status == errSecSuccess)
```

`biometryCurrentSet` es más estricto que `biometryAny`. Vincula el acceso a las huellas o los datos faciales inscritos actualmente, por lo que cambiar el conjunto biométrico invalida el elemento protegido. `biometryAny` acepta cualquier dato biométrico inscrito y no ofrece la misma señal cuando cambia el conjunto. Apple enumera ambas banderas en `SecAccessControlCreateFlags`; elige la primera cuando una huella nueva deba obligar a aprovisionar el elemento de forma deliberada.

La recuperación necesita un contexto de autenticación y un aviso de operación que describa la acción en términos comprensibles:

```swift
import LocalAuthentication
import Security

let context = LAContext()
context.localizedReason = "Use release-bot for the requested deployment action"

let query: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: "com.example.agent-gateway",
    kSecAttrAccount: "release-bot",
    kSecReturnData: true,
    kSecMatchLimit: kSecMatchLimitOne,
    kSecUseAuthenticationContext: context,
    kSecUseOperationPrompt: "Authorize credential use"
]

var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &result)
```

El código queda incompleto a propósito en un aspecto: no indica qué hacer con `result`. Una app convencional podría convertirlo en `Data`, crear una cabecera `Authorization` y continuar. Una puerta de enlace para agentes debe garantizar que esos datos permanezcan dentro del proceso que realiza la llamada autenticada. No los devuelvas en la respuesta MCP. No los guardes en un archivo temporal. No los registres cuando falle la solicitud.

Ten cuidado con las ventanas de reutilización. El ejemplo de Apple muestra que la validación de Touch ID puede reutilizar durante un tiempo configurado un evento reciente de desbloqueo del dispositivo, hasta cinco minutos. Esa comodidad ayuda a las apps normales a evitar avisos repetidos. En un punto de control de un agente puede borrar el momento de aprobación deliberada que querías exigir. No uses una ventana de gracia para credenciales que requieren aprobación en cada uso, salvo que hayas decidido conscientemente aceptarla y lo hayas documentado.

Hay otro límite que conviene mencionar. Los datos biométricos pueden fallar, no estar disponibles o bloquearse después de varios intentos fallidos. Apple ofrece políticas que permiten recurrir al código del dispositivo y políticas que exigen datos biométricos. Decide cuál necesita tu afirmación de seguridad. Si dices «Touch ID en cada despliegue de producción», aceptar silenciosamente una alternativa distinta cambia esa afirmación y debería cambiar el texto de la interfaz.

## Sigue una instrucción maliciosa hasta el punto en que gana

Un agente no necesita un exploit espectacular para abusar de una credencial. Necesita una instrucción plausible, una capacidad amplia y un canal de salida que devuelva más de lo que el usuario pretendía.

Imagina un agente de programación que ayuda con una solicitud de cambios. Lee un documento del repositorio añadido por un atacante. El documento dice que la comprobación de la versión requiere ejecutar un script auxiliar. El script usa la sesión existente de la CLI de la nube del desarrollador, enumera las credenciales de despliegue y envía una solicitud codificada a un destino externo. El agente tiene permiso para ejecutar comandos de shell y ha heredado `AWS_PROFILE`, `GH_TOKEN` o acceso a un agente SSH.

El primer fallo ocurrió antes de ejecutar el script: el desarrollador entregó al agente credenciales ambientales. El segundo ocurrió cuando el ejecutor de herramientas permitió que el agente eligiera destinos de red arbitrarios. El tercero ocurrió cuando los registros y la salida de los comandos devolvieron material de autenticación o detalles de la sesión al contexto del agente.

Touch ID al iniciar sesión no rescataría este diseño. La persona puede haberse autenticado una hora antes y haberse alejado. Un elemento de Keychain protegido solo por «dispositivo desbloqueado» puede quedar disponible para un proceso que el usuario nunca quiso autorizar para esta tarea. La propia Apple advierte de que el acceso con el dispositivo desbloqueado puede no ser lo bastante restrictivo para todos los casos de uso.

Ahora cambia la arquitectura. El agente solicita una acción con estos campos:

```json
{
  "channel": "http",
  "credential": "release-bot",
  "method": "POST",
  "url": "https://api.example.com/releases",
  "body": {"branch": "feature/fix-ci"}
}
```

La puerta de enlace resuelve `release-bot` internamente. Compara el destino solicitado con la acción que va a realizar, pide autorización si la credencial la requiere, inyecta la cabecera por sí misma y después registra la acción. El agente recibe el estado HTTP y una respuesta redactada. Nunca ve el valor de la cabecera.

El documento del repositorio aún puede convencer al agente de solicitar un despliegue incorrecto. Por eso importan la revisión del destino y de la acción. Pero el documento no puede ordenar al agente que extraiga un token que nunca tuvo. Tampoco puede reutilizar el token contra otro servicio una vez terminada la llamada aprobada.

Esto reduce de forma significativa el radio de impacto, pero no es magia. Un agente comprometido que tenga aprobación para desplegar aún puede desplegar algo dañino. El sistema ha limitado el robo de credenciales y ha hecho que la acción sea atribuible; no ha resuelto la revisión de código malicioso ni ha sustituido el criterio humano.

Cuando reviso configuraciones de agentes, busco el primer lugar en el que una instrucción puede convertirse en una cadena secreta. Normalmente ahí es donde debe estar la solución.

## Los identificadores de capacidades son más seguros que las cadenas secretas

Un agente debería solicitar una capacidad con nombre y parámetros de acción estructurados, mientras un proceso local de confianza resuelve esa capacidad a una credencial y realiza el efecto secundario.

La palabra «capacidad» se usa de forma imprecisa. En este caso significa una referencia útil solo dentro de la puerta de enlace, como `release-bot`, `staging-ssh` o `billing-read`. No es un alias de token que el agente pueda canjear por el token. No es una variable de plantilla que se expanda a un valor de entorno. Es un selector que se pasa a un proceso que conserva el acceso exclusivo a la credencial.

Esta decisión impone disciplina a la interfaz de herramientas. Las herramientas HTTP deben aceptar el método, la URL, las cabeceras que el agente pueda proporcionar de forma segura y el cuerpo. La inyección de credenciales debe producirse después de la validación, dentro de la puerta de enlace. Las herramientas SSH deben aceptar un host, un usuario, un comando y la identidad de credencial seleccionada, y después invocar un asistente que controle la ruta de autenticación. No deben devolver una ruta `IdentityFile` ni ofrecer una operación genérica de «leer secreto».

Aquí es donde una interfaz sencilla sale ganando. Un shell general que herede todas las credenciales del desarrollador admitirá más herramientas desde el primer día. También hará casi imposible responder qué agente usó qué cuenta para qué solicitud saliente. Una interfaz HTTP y SSH limitada tiene menos alcance inicial, pero conserva los datos necesarios para tomar una decisión de seguridad.

Sallyport sigue esta estructura: el agente usa el adaptador stdio incluido `sp mcp` para solicitar acciones HTTP o SSH, mientras la app mantiene las credenciales API y SSH dentro de su bóveda cifrada y realiza la acción por sí misma. El agente recibe el resultado, no las credenciales en texto plano.

La limitación es real. Una herramienta que solo conoce HTTP y SSH no cubrirá automáticamente todas las aplicaciones de escritorio, clientes de bases de datos, registros de paquetes o binarios locales del entorno de un desarrollador. Añadir un canal debería exigir el diseño de su modelo de acciones, su comportamiento de redacción, sus reglas de autorización y sus campos de auditoría. Un interruptor amplio de «ejecutar cualquier cosa con mi inicio de sesión» es más fácil de lanzar y más difícil de defender.

Usa etiquetas de credenciales explícitas que indiquen el alcance previsto. `prod-deployer` es mejor que `token-4`. `github-readonly-org` es mejor que `github`. La etiqueta forma parte de la decisión humana y del registro de auditoría, por lo que la ambigüedad se convierte en un problema operativo y no solo de nomenclatura.

Mantén el formato de la solicitud lo bastante limitado para que la puerta de enlace pueda mostrarlo sin interpretarlo. Un revisor puede entender `POST https://api.example.com/releases`. No puede deducir de forma fiable el efecto de un bloque Base64 canalizado a través de un envoltorio de shell.

## Un registro de auditoría debe describir la acción sin copiar el secreto

Una puerta de enlace de credenciales necesita dos tipos de registros: la ejecución del agente que obtuvo autoridad y cada efecto secundario que intentó realizar. Un único flujo de registros del terminal no puede proporcionar ambas cosas sin perder contexto o filtrar material sensible.

Registra la ejecución cuando un proceso nuevo del agente solicite acceso. Captura la identidad del proceso disponible para la puerta de enlace, su autoridad de firma de código, la hora de inicio, la decisión de aprobación y el estado de revocación. Si el usuario revoca la ejecución, las solicitudes posteriores de ese proceso deben fallar aunque el proceso siga activo.

Registra cada acción por separado. Para HTTP, conserva la etiqueta de la credencial, el método, el destino, el estado, la duración y un resumen elegido cuidadosamente del cuerpo. Para SSH, conserva la etiqueta de la credencial, el host, el usuario remoto, el comando, el código de salida y la duración. No registres cabeceras `Authorization`, valores portador, bytes de claves privadas, cuerpos completos de solicitudes que contengan datos de clientes ni la salida de comandos sin restricciones.

Los registros que contienen secretos se convierten en otra bóveda con controles de acceso peores.

La evidencia de manipulación importa porque un incidente con un agente suele comenzar con una cronología discutida: «¿El agente llamó a este endpoint?», «¿Se aprobó la sesión?», «¿Alguien modificó el historial local?». Un registro de eventos encadenado mediante hashes ofrece un objeto concreto para comprobar después. El verificador debería funcionar sin necesidad de descifrar cada evento; de lo contrario, la persona que comprueba la integridad tendría que recibir primero los datos sensibles que el registro debía proteger.

Sallyport proyecta sus diarios de sesiones y actividad desde un único registro de auditoría cifrado, encadenado mediante hashes y protegido contra escritura, y `sp audit verify` comprueba la cadena sin conexión y sin una clave de la bóveda. Esta propiedad resulta útil porque revisar la integridad no debería requerir acceso a los secretos del desarrollador.

Una cadena de hashes no impide que una máquina comprometida intente realizar una acción incorrecta. Hace más difícil reescribir silenciosamente la secuencia registrada y ofrece a la investigación una sucesión coherente de acontecimientos. No la presentes como un mecanismo de prevención.

Apple Platform Security y la documentación de seguridad de Apple para desarrolladores son lecturas útiles porque separan las protecciones de hardware, los controles del sistema y las responsabilidades de la aplicación. Esa separación es exactamente lo que necesita la seguridad de los agentes. El hardware seguro puede controlar el acceso, pero la aplicación sigue eligiendo qué envía por la red y qué escribe en el disco.

## El acceso de producción necesita menos rutas, no suposiciones más inteligentes

El despliegue más seguro de agentes de programación basados en IA empieza con un conjunto pequeño de credenciales con nombre, destinos conocidos, una identidad de sesión visible y una clase de acción irreversible que requiera una aprobación nueva. El acceso ambiental amplio es una invitación a descubrir tu modelo de amenazas durante una interrupción del servicio.

Usa esta revisión antes de permitir que un agente toque una credencial:

1. Confirma que el agente no puede leer el secreto mediante variables de entorno, archivos, un asistente de credenciales, la salida de una herramienta o un proceso hijo.
2. Confirma que el proceso de confianza realiza la solicitud HTTP o la autenticación SSH e inyecta la credencial después de que el agente envíe parámetros estructurados.
3. Configura el bloqueo de la bóveda para denegar cualquier acción respaldada por secretos. Pruébalo con la app bloqueada, no solo con la pantalla del Mac bloqueada.
4. Exige una aprobación nueva de sesión cuando se inicie un proceso nuevo del agente y haz que la aprobación identifique su autoridad de firma.
5. Marca las credenciales con alcance de despliegue, administración, destrucción o acceso SSH amplio para que requieran autorización en cada uso.

Prueba los casos desagradables. Añade un secreto falso como `canary-agent-secret-9f31` a una credencial que no sea de producción. Pide al agente que inspeccione el repositorio, ejecute las pruebas e informe de la salida de las herramientas. Después busca esa cadena exacta en su transcripción, el historial del terminal, los directorios temporales, los registros, el entorno de los procesos hijos y los registros de auditoría. Si aparece fuera del proceso de la bóveda, el diseño ha entregado al agente una ruta hacia el secreto.

Haz lo mismo con la revocación. Aprueba una ejecución, realiza una solicitud inofensiva, revoca la ejecución mientras el proceso sigue abierto y vuelve a intentar la misma solicitud. La segunda solicitud debe fallar antes de llegar al servicio remoto. Un control de revocación que solo entra en vigor después de reiniciar es papeleo, no contención.

No uses un aviso biométrico como decoración alrededor de una exportación de credenciales. Coloca el aviso en el límite donde la persona autoriza un proceso o una acción concretos y mantén la credencial en el lado protegido. Así Secure Enclave y Touch ID se convierten en controles útiles para los agentes de programación basados en IA, en lugar de formar parte de una historia tranquilizadora sobre el almacenamiento.
