8 min de lectura

Aislamiento de credenciales en pools de conexiones HTTP para agentes de IA

El aislamiento de credenciales en pools de conexiones HTTP evita que cookies, desafíos y estado de cuenta sigan un socket reutilizado hacia la acción equivocada de un agente.

Aislamiento de credenciales en pools de conexiones HTTP para agentes de IA

Una conexión HTTP reutilizada no implica automáticamente una fuga de credenciales. Tratarla como algo inocuo hace que los equipos pasen por alto el fallo real: un objeto de cliente acumula cookies, respuestas a desafíos, cabeceras de redirección, identidad del proxy o identidad TLS, y una solicitud posterior toma prestado ese estado con una credencial distinta.

El aislamiento de credenciales en un pool de conexiones HTTP consiste en decidir exactamente qué estado puede pasar de una solicitud a otra e impedir que el resto se comparta. Solo el host de destino suele ser un límite demasiado amplio para un ejecutor de acciones o un agente que puede actuar para varias cuentas. Un cliente rápido que de vez en cuando envía la cookie de la cuenta equivocada es peor que uno más lento, porque el error parece una solicitud correcta y normal.

Un socket es transporte, no propiedad de una cuenta

Una conexión TCP transporta bytes para un origen. No ofrece a una aplicación un límite fiable entre cuentas. HTTP/1.1 reutiliza la conexión de forma secuencial y HTTP/2 puede ejecutar muchos flujos a la vez, pero ninguno de los dos protocolos dice que todas las solicitudes de una conexión pertenezcan al mismo usuario o cuenta de servicio.

Esta distinción importa porque las bibliotecas de cliente suelen ocultar asuntos no relacionados tras una interfaz cómoda llamada sesión, agente, cliente o transporte. Entonces los desarrolladores crean uno de esos objetos por host y añaden un token bearer antes de cada llamada. La cabecera bearer puede ser correcta siempre, mientras un almacén de cookies, una caché de desafíos Digest o el contexto del proxy pertenece silenciosamente a la última cuenta que usó el objeto.

RFC 9110 trata la autenticación como un comportamiento de solicitud y espacio de protección. Un cliente responde a un desafío para un origen, esquema y ámbito concretos. No dice que una conexión abierta sea dueña de una cuenta humana o de servicio. Si tu cliente asume esa propiedad, la suposición proviene de tu código o de su biblioteca, no de HTTP.

Reutilizar conexiones sigue siendo conveniente. Evita handshakes repetidos, reduce el agotamiento de puertos y ayuda a un servicio remoto bajo carga. La regla correcta es más precisa: reutiliza una conexión solo entre solicitudes cuyo estado a nivel de conexión y estado gestionado por el cliente sean compatibles de forma intencionada.

Los estados que conviene separar se dividen en dos grupos:

  • El estado de la solicitud incluye Authorization, Cookie, cabeceras de cuenta, cuerpos de solicitud y valores de idempotencia. El llamador debe construirlos de nuevo para cada acción.
  • El estado de conexión y cliente incluye una entrada del pool, sesión de proxy, selección de certificado de cliente, comportamiento de redirección, cookies, cachés de desafíos y ajustes de protocolo. El cliente debe delimitar o desactivar cada uno de forma deliberada.

Una credencial bearer usada solo en una cabecera Authorization puede compartir una conexión TCP con otra credencial bearer si la biblioteca envía cabeceras por solicitud y no conserva estado de cuenta. Es una afirmación limitada, no una autorización general. En cuanto el servicio también establece una cookie de sesión, redirige a otro host o solicita un certificado de cliente, el diseño del pool necesita revisarse de nuevo.

Las cookies son estado de cuenta aunque sus nombres parezcan inofensivos

Un almacén de cookies es la fuente más común de cruces accidentales entre cuentas. Los equipos ven una API con token bearer y suponen que las cookies no importan, hasta que un balanceador de carga, un punto de inicio de sesión interactivo o un servicio antiguo añade Set-Cookie a una respuesta. Un cliente HTTP general suele almacenarla salvo que le indiques lo contrario.

RFC 6265 define cómo un agente de usuario selecciona cookies por dominio, ruta, atributos de seguridad y reglas relacionadas. Su modelo de almacenamiento no tiene un campo para «el registro de credenciales que recibió esta cookie». Por tanto, dos credenciales bearer que llaman al mismo host y ruta pueden cumplir los requisitos para la misma cookie almacenada. El protocolo sigue sus reglas perfectamente mientras tu aplicación cruza cuentas.

Considera este rastro de un servicio de prueba. La cuenta alpha usa un token bearer y recibe una cookie de afinidad:

GET /v1/whoami HTTP/1.1
Host: api.example.test
Authorization: Bearer alpha-token

HTTP/1.1 200 OK
Set-Cookie: route=alpha-node; Path=/; Secure; HttpOnly
Content-Type: application/json

{"account":"alpha"}

La siguiente acción selecciona beta. Si un almacén compartido envía la cookie guardada, la solicitud lleva dos señales de propiedad:

GET /v1/whoami HTTP/1.1
Host: api.example.test
Authorization: Bearer beta-token
Cookie: route=alpha-node

El mejor resultado es que el servicio rechace la discrepancia. Los servicios menos cuidadosos enrutan la solicitud de beta mediante la sesión persistente de alpha, la vinculan al contexto incorrecto del lado del servidor o aceptan la cabecera que resulte ganadora. El cliente no puede confiar en que el servicio remoto rescate una solicitud mezclada.

Para las API de máquina a máquina, mi punto de partida es contundente: desactiva el almacenamiento ambiental de cookies. Si una API realmente necesita cookies, crea un almacén para un único registro de credenciales y un único contexto de servicio previsto. No lo compartas solo porque coincida la cadena del host.

Revisa también las entradas de la solicitud antes de inyectar la credencial. Un agente, plugin o llamador no debe poder proporcionar su propia cabecera Cookie que llegue intacta a una llamada con credenciales. Elimina las cabeceras de autenticación y cookies ambientales, y añade solo las cabeceras que permite la definición de la acción. De lo contrario, habrás creado un aislamiento alrededor de una puerta que los llamadores pueden sortear.

Los desafíos de autenticación necesitan un llamador actual

Una respuesta 401 no es solo un código de error. Puede invitar al cliente a reintentar tras seleccionar credenciales, y esa lógica de selección suele vivir fuera del código que creó la solicitud original. Los gestores de autenticación Basic y Digest son especialmente propensos a este patrón, pero el middleware personalizado puede cometer el mismo error con tokens bearer.

Una implementación deficiente conserva un currentCredential mutable en un cliente compartido. La solicitud A recibe un desafío, el gestor carga el secreto de alpha y el cliente reintenta. La solicitud B empieza antes de que termine el reintento y cambia currentCredential a beta. Ahora el gestor de desafíos tiene una condición de carrera que los conjuntos de pruebas no detectan porque ejecutan las solicitudes de una en una.

No lo arregles colocando un bloqueo alrededor de un campo global de credenciales. El bloqueo serializa el trabajo y sigue dejando la identidad en el objeto equivocado. Vincula el registro de credenciales al contexto de la solicitud, transpórtalo en cada reintento y rechaza cualquier reintento cuyo contexto no esté presente.

La autenticación Digest merece especial desconfianza. Incluye nonces, ámbitos, contadores y una respuesta calculada. Esos valores están ligados al espacio de protección desafiado, no a un cliente compartido genérico. La autenticación Basic es más sencilla en la red, pero una caché que la añade automáticamente a solicitudes posteriores necesita el mismo alcance de credenciales.

Usa un punto de prueba que devuelva un ámbito o desafío distinto para cada cuenta. Después ejecuta solicitudes simultáneas y comprueba que cada reintento use el registro de credenciales asociado a su propia acción. Una prueba que solo comprueba el estado final 200 es demasiado débil. Captura la identidad de la solicitud en el servidor y falla cuando el desafío de alpha produce un intento de autorización de beta.

No confundas un 403 con un desafío. Los servidores usan 403 para muchas decisiones de autorización y los clientes no deben interpretarlo como permiso para buscar otra credencial. La alternativa automática entre credenciales convierte un fallo de acceso en sondeo de cuentas, algo inseguro y difícil de auditar.

HTTP/2 hace más fáciles de ocultar los errores de propiedad

HTTP/2 permite que varios flujos compartan una conexión TLS. Es eficiente, pero elimina la antigua señal visual de una solicitud esperando detrás de otra. Un cliente puede enviar acciones de alpha y beta al mismo tiempo, y sus cabeceras solo permanecen separadas si la biblioteca las modela como solicitudes distintas hasta el final.

RFC 9113 prohíbe campos de cabecera específicos de conexión de HTTP/1.1, como Connection, en solicitudes HTTP/2. Esa regla no hace que las cookies o la autenticación tengan alcance de cuenta. Solo evita que un tipo de comportamiento entre saltos se exprese como cabeceras de solicitud normales. Tu cliente sigue siendo responsable de seleccionar cookies, reintentos, redirecciones y cualquier middleware compartido.

La coalescencia de conexiones HTTP/2 añade otra complicación. Algunos clientes pueden usar una conexión segura para más de un origen cuando las comprobaciones de certificado y nombre lo permiten. La coalescencia no repite por sí sola una cabecera Authorization. Sí significa que una identidad de pool basada en una comparación superficial de hosts puede no describir la conexión que realmente eligió el cliente. Separa las decisiones de autorización de origen de la optimización del transporte y prueba el comportamiento de la biblioteca que despliegas.

La compresión de cabeceras también preocupa a la gente por el motivo equivocado. HPACK comprime cabeceras dentro de una conexión HTTP/2 y QPACK hace algo relacionado para HTTP/3. Un cliente conforme no decodifica el valor Authorization de una solicitud anterior en una solicitud posterior. El peligro es reutilizar directamente estado en el código de tu cliente, junto con posibles problemas de metadatos en modelos de amenaza poco habituales. Marca las cabeceras sensibles como no indexables cuando tu biblioteca ofrezca ese control, pero no lo presentes como sustituto del aislamiento de solicitudes.

HTTP/3 cambia el transporte de TCP a QUIC. No cambia la regla de propiedad. Un pool que permite que un almacén de cookies o una caché de credenciales circule libremente entre acciones sigue siendo incorrecto sobre QUIC.

Los certificados de cliente son distintos. Un certificado de cliente TLS se elige al establecer la conexión, así que realmente está ligado a ella. Nunca multiplexes identidades de certificado de cliente distintas mediante la misma conexión o entrada de pool. Incluye el registro del certificado en la identidad del pool o asigna a cada certificado una instancia de cliente independiente. La misma cautela se aplica a un proxy que autentica una conexión antes de reenviar solicitudes.

Haz explícita la identidad del pool en el código

Mantén los tokens fuera de los agentes
Sallyport inyecta la credencial de API seleccionada, de modo que los agentes nunca reciben el token bearer.

La identidad del pool debe incluir todo valor que cambie lo que una conexión nueva puede significar de forma segura. Para una API solo con bearer, cookies desactivadas y sin identidad de proxy, puede bastar el origen más un identificador de registro de credenciales. Para un certificado de cliente, una ruta autenticada por proxy o un perfil TLS especial, incluye también esos identificadores.

No uses el texto secreto como identificador del mapa. Crea copias innecesarias del secreto en memoria y registros, complica la rotación y tienta a alguien a imprimir el mapa durante la depuración. Usa un ID opaco de registro de credenciales cuyo ciclo de vida controle tu bóveda o registro de acciones.

Este esquema en TypeScript muestra la forma. Usa Pool de Undici, pero el límite se aplica a cualquier biblioteca de cliente. credentialId es un identificador, no el valor bearer.

import { Pool } from "undici";

type Boundary = {
  origin: string;
  credentialId: string;
  proxyId?: string;
  clientCertificateId?: string;
};

const pools = new Map<string, Pool>();

function poolId(boundary: Boundary): string {
  return [
    boundary.origin,
    boundary.credentialId,
    boundary.proxyId ?? "direct",
    boundary.clientCertificateId ?? "none"
  ].join("\u001f");
}

function poolFor(boundary: Boundary): Pool {
  const id = poolId(boundary);
  let pool = pools.get(id);
  if (!pool) {
    pool = new Pool(boundary.origin);
    pools.set(id, pool);
  }
  return pool;
}

function headersFor(token: string, input: HeadersInit = {}): Headers {
  const headers = new Headers(input);
  headers.delete("authorization");
  headers.delete("cookie");
  headers.delete("proxy-authorization");
  headers.set("authorization", `Bearer ${token}`);
  return headers;
}

El esquema evita que una cabecera Authorization o Cookie proporcionada por el llamador viaje junto con la credencial inyectada. No implementa un almacén de cookies, redirecciones, envío mediante proxy ni configuración de certificados. La omisión es intencionada: cada uno requiere una decisión consciente en lugar de un valor predeterminado accidental.

Si cada credencial habla con el mismo punto público anónimo, quizá no hagan falta pools separados. Si el punto ve cuentas distintas, usa entradas de pool separadas hasta que tengas una razón concreta y pruebas para compartirlas. Un pool cuesta poco frente a una confusión entre cuentas.

La rotación necesita su propia regla. Cuando cambia un registro de credenciales, deja de asignar acciones nuevas a su pool antiguo. Permite que las solicitudes ya enviadas terminen con su registro original si la semántica de la acción lo permite, y luego cierra ese pool. No redirijas un reintento en curso al registro de reemplazo. Una rotación cambia la autoridad, no corrige el significado del trabajo ya iniciado.

Una recomendación popular dice que hay que desactivar keep-alive en todas partes. Reduce el número de objetos de transporte compartidos, por lo que parece seguro después de un incidente. También oculta si las cookies, el código de redirecciones y las cachés de desafíos están bien delimitados, a la vez que crea carga de handshake nueva para cada solicitud. Conserva el pooling, aclara su propiedad y prueba los recorridos difíciles.

Las redirecciones pueden enviar una solicitud limpia al lugar equivocado

Bloquea la puerta de enlace de acciones
La bóveda cifrada permanece bloqueada tras Secure Enclave y Touch ID, e impide acciones hasta que la desbloquees.

La gestión de redirecciones es un segundo cliente oculto dentro del primero. Una biblioteca recibe una respuesta 301, 302, 303, 307 o 308, construye otra solicitud y decide qué cabeceras se conservan. Si toma esa decisión después de tu código de límite de credenciales, puede llevar una cabecera de cuenta o cookie a un destino no previsto.

Para acciones con credenciales inyectadas, empieza con las redirecciones desactivadas o manuales. Inspecciona el origen de destino antes de seguir una. Permite redirecciones solo cuando la definición de la acción las espere, el destino esté dentro de su conjunto de orígenes aprobados y el cliente reconstruya las cabeceras desde el contexto de credenciales original, en vez de copiar un conjunto de cabeceras antiguo.

El código de estado importa. Un 303 puede cambiar una solicitud a GET en el comportamiento habitual de un navegador, mientras que 307 y 308 conservan método y cuerpo. Un cliente que reintenta y los trata todos igual puede repetir una escritura con credenciales en otro punto. Es un problema de integridad de la acción incluso cuando ningún secreto cruza un origen.

Elimina las cabeceras Authorization y Cookie en toda redirección entre orígenes. En muchos clientes ya es el comportamiento predeterminado, pero los valores predeterminados no son el resultado de una prueba. Verifica la versión y configuración que usas. Las redirecciones dentro del mismo origen también necesitan una lista de rutas permitidas cuando una credencial concede más autoridad de la que debería recibir el punto original.

La autenticación de proxy necesita el mismo tratamiento. Proxy-Authorization pertenece a la ruta de proxy seleccionada, no al servicio de origen. Nunca la coloques en un mapa genérico de cabeceras predeterminadas. Si dos acciones llegan a la misma API a través de proxies autenticados distintos, separa sus entradas de pool y haz visible la selección de proxy en el registro de acción.

Una prueba de aislamiento fallida tiene una forma reconocible

La mayoría de los equipos prueban que alpha puede llamar a la API y que beta puede hacerlo. Eso demuestra que las credenciales funcionan. No demuestra que el mismo cliente de larga duración pueda pasar de alpha a beta sin llevar equipaje.

Crea un pequeño servidor de prueba con dos cuentas y haz que informe de lo que recibe. Debe devolver la cuenta derivada de Authorization, repetir la cabecera Cookie entrante, establecer una cookie específica de la cuenta y exponer un punto de redirección. El servicio debe rechazar una solicitud cuando la etiqueta de cuenta de la cookie difiera de la etiqueta de cuenta bearer. El rechazo hace visible el error en lugar de permitir que una capa de afinidad lo oculte.

Ejecuta esta secuencia con la configuración exacta del cliente que usas en producción:

  1. Envía una solicitud de alpha a través de un pool nuevo. Confirma que la respuesta establece route=alpha.
  2. Envía una solicitud de beta mediante la búsqueda de pool que usa producción. Comprueba que el servidor ve autorización de beta y una cabecera Cookie vacía, salvo que beta tenga un almacén separado que contenga solo cookies de beta.
  3. Inicia solicitudes de alpha y beta simultáneamente sobre HTTP/2. Haz que cada punto se pause antes de responder para que sus flujos se solapen, y luego comprueba identidades y cookies por separado.
  4. Devuelve un desafío 401 a cada cuenta y verifica que el reintento use su registro de credenciales original.
  5. Devuelve una redirección a otro origen y verifica que la solicitud posterior no tenga cabecera de autorización ni cookie.

Las comprobaciones deben registrar más que códigos de estado. Captura el ID del registro de credenciales seleccionado, el ID del pool, el origen, la versión de protocolo, el destino de redirección, la presencia de Cookie y la cuenta de la respuesta. No registres la credencial en sí. Cuando falle la prueba, estos campos indican si la fuga provino de la búsqueda del pool, cabeceras ambientales, un almacén o middleware de reintento.

Prueba también la rotación de credenciales. Inicia una solicitud que espere en el servidor, sustituye el registro de credenciales y emite una solicitud nueva. La acción nueva debe usar una identidad de pool nueva. Decide y documenta si la acción que espera termina con su autoridad original o se cancela. Ambas opciones pueden ser defendibles; cambiar silenciosamente la autoridad durante el vuelo no lo es.

Ejecuta la prueba mediante cualquier configuración de proxy que admitas. El estado del proxy y el estado del origen suelen vivir en capas distintas de la biblioteca, lo que convierte aquí una prueba unitaria tranquilizadora en una prueba de integración útil.

Las acciones de agentes necesitan una superficie de autoridad más pequeña

Controla el uso de claves de alto riesgo
Exige Touch ID o un clic para cada uso de una clave de API sensible.

Un agente autónomo de programación debería solicitar una acción como «llama a esta API aprobada con el registro de credenciales billing-read». No debería recibir un token ni construir un cliente amplio y de larga duración que pueda conservar estado de sesión cuando cambie la tarea. El ejecutor de acciones puede vincular el destino aprobado, el método, el registro de credenciales y el límite de cliente antes de enviar un solo byte.

Sallyport sigue esa separación al guardar las credenciales de API y SSH en su bóveda cifrada y ejecutar la acción por sí mismo, de modo que el agente recibe el resultado en vez de la credencial. Esta contención solo sirve si la ruta HTTP también trata cada registro de credenciales como su propio contexto de autoridad.

La aprobación no sustituye el aislamiento del cliente. Un operador puede aprobar una acción beta legítima mientras un almacén de cookies compartido y descuidado la convierte en una solicitud mezclada de alpha y beta. El registro de aprobación documenta entonces una acción distinta de la que el operador quería permitir. Sitúa el límite debajo de la pantalla de aprobación, donde realmente ocurren las cabeceras, los reintentos y la selección de transporte.

Para una puerta de enlace de acciones, registra el ID del registro de credenciales y la identidad del pool en el evento de auditoría, pero nunca el secreto. Si un llamador informa de una discrepancia de cuenta, debes determinar si el ejecutor eligió la credencial correcta y si adjuntó solo el estado que pertenece a ese registro. Un historial de actividad a prueba de manipulaciones ayuda a investigar esa cuestión; no vuelve seguro un diseño de cliente ambiguo.

El primer cambio práctico es sencillo: localiza cada cliente HTTP compartido y enumera qué recuerda además de una conexión abierta. Si la respuesta incluye cookies, respuestas a desafíos, redirecciones, certificados de cliente o identidad de proxy, asigna un propietario explícito a cada elemento antes de que la siguiente rotación de tokens o ejecución simultánea de agentes encarezca el error.

FAQ

¿Puede HTTP conservar una cabecera Authorization en una conexión reutilizada?

Una conexión TCP o TLS normalmente no conserva una cabecera HTTP Authorization cuando termina la solicitud. La fuga aparece cuando el contenedor del cliente, el almacén de cookies, el gestor de redirecciones, la configuración del proxy o una credencial ligada a la conexión reutilizan estado que pertenecía al llamador anterior.

¿Es seguro compartir conexiones HTTP/2 entre cuentas distintas?

HTTP/2 multiplexa varios flujos de solicitudes en una conexión, por lo que una conexión es un límite de propiedad poco fiable cuando las credenciales difieren. Mantén las credenciales acotadas a cada solicitud y comparte una conexión solo después de demostrar con pruebas que el cliente no transporta estado de cuenta entre flujos.

¿Deben compartir un almacén de cookies credenciales de API diferentes?

No. Un almacén de cookies indexa las cookies por host, dominio, ruta y atributos relacionados, no por la credencial bearer que las obtuvo. Un almacén compartido puede enviar la cookie de sesión de una cuenta junto con la cabecera de autorización de otra.

¿Pueden dos certificados de cliente compartir un pool de conexiones HTTP?

No uses el mismo pool para solicitudes que autentican la conexión TLS con certificados de cliente distintos. El certificado se selecciona durante el handshake, antes de que exista una solicitud HTTP individual.

¿Los desafíos 401 generan riesgos de fuga de credenciales?

Una respuesta 401 puede hacer que un cliente elija credenciales desde una caché, un gestor de solicitud o el contexto de una solicitud anterior. Prueba expresamente las solicitudes con desafío y asegúrate de que el reintento seleccione solo las credenciales vinculadas a la acción actual.

¿Cómo debo nombrar los pools cuando rotan los tokens?

Un pool de conexiones debe usar un identificador estable del registro de credenciales, no el texto del token ni solo el host de destino. Al sustituir ese registro, retira su pool anterior para que el trabajo nuevo no herede su contexto de transporte.

¿Debo desactivar el pool de conexiones para evitar el cruce de credenciales?

No. Desactivar la reutilización puede reducir el síntoma, pero deja sin revisar los almacenes de cookies compartidos, el código de redirecciones y las cachés de credenciales, además de añadir latencia y presión sobre las conexiones. Usa un límite deliberado y demuéstralo con pruebas.

¿La autenticación de proxy necesita su propio límite de pool?

La autenticación del proxy es distinta de la autenticación del origen, pero también puede almacenarse en caché o negociarse en una conexión de proxy compartida. Incluye la identidad del proxy en la identidad del pool y pruébala independientemente de la credencial del origen.

¿Qué debe comprobar una prueba de integración para el aislamiento de credenciales?

Comprueba la identidad devuelta por el servicio, inspecciona si llegó una cabecera Cookie y registra qué registro de credenciales seleccionó el cliente. Ejecuta la misma prueba con HTTP/1.1 y HTTP/2 si el cliente de producción puede usar ambos.

¿Cómo reduce una puerta de enlace de agentes de IA la exposición de credenciales HTTP?

Una puerta de enlace debe recibir la acción solicitada y la referencia de la credencial, inyectar ella misma el secreto y devolver el resultado sin exponerlo al agente. Aun así, la puerta de enlace necesita límites correctos en el cliente: contener el secreto no corrige un almacén de cookies compartido y descuidado.

Sallyport

Sallyport ejecuta llamadas de API y comandos SSH por tu agente de IA. Las claves se quedan en una bóveda local de tu Mac; tú apruebas cada ejecución y cada acción queda en un registro sellado.

© 2026 Sallyport · Código abierto bajo Apache-2.0 · Oleg Sotnikov