# ¿Puede el userinfo de una URL ocultar el destino de tu API?

Un agente que acepta una URL arbitraria puede dirigirse a un lugar que su operador no pretendía. El userinfo de URL facilita ese error porque coloca un nombre de host conocido antes de un signo `@`, mientras el destino real queda después. Si tu pantalla de aprobación, lista de permitidos o vista de auditoría trata toda la cadena como una etiqueta que parece un host, una solicitud puede parecer aprobada y aun así salir hacia otro servidor.

Trata userinfo como una entrada no permitida para acciones de API salientes, salvo que tengas un motivo de compatibilidad limitado y documentado para admitirlo. Analiza la URL una sola vez en el límite, rechaza un campo userinfo no vacío antes de que las credenciales entren en la solicitud y toma todas las decisiones posteriores a partir de los campos analizados. No es un truco exótico de URL. Es sintaxis normal frente a una interfaz de revisión que pide a las personas leer demasiada puntuación demasiado deprisa.

## El nombre de host está después del último signo arroba

En una URL HTTP absoluta, la autoridad está entre `//` y el siguiente `/`, `?` o `#`. RFC 3986 describe esa autoridad como userinfo opcional, luego `@`, después host y, opcionalmente, puerto. Por tanto, el host no es cualquier texto que aparezca primero tras `//`.

Considera esta solicitud:

```text
https://api.example.com@collector.invalid/v1/charges
```

Alguien que lee el principio por encima puede reconocer `api.example.com` y detenerse ahí. Un analizador de URL conforme separa los elementos así:

```text
scheme:   https
userinfo: api.example.com
host:     collector.invalid
port:     443
path:     /v1/charges
```

La conexión TCP y la verificación del nombre de host TLS usan `collector.invalid`. La cadena anterior a `@` no identifica el servidor remoto. Es userinfo, una parte heredada de la gramática de URL que antes se usaba para nombres y contraseñas.

El mismo problema aparece de una forma más familiar:

```text
https://billing.example.com:wrong@evil.invalid/invoices
```

Todo lo anterior a `@` sigue siendo userinfo, incluidos los dos puntos y el texto posterior. El host remoto sigue siendo `evil.invalid`. Un revisor no puede deducir con seguridad el destino a partir de la primera subcadena que parece un nombre de host en una URL sin procesar.

No intentes resolverlo enseñando a los revisores a fijarse en ese carácter. Las personas cometen este error durante la revisión de incidentes, al final de un día largo y cuando un agente genera muchas solicitudes de acción. Un control que depende de un análisis visual perfecto es débil.

El estándar URL que usan los navegadores y muchos entornos de ejecución tiene el mismo resultado práctico para esquemas especiales como HTTPS: los campos de nombre de usuario y contraseña analizados están separados del nombre de host. El comportamiento exacto del analizador puede variar ante entradas malformadas y escapes, otra razón para analizar con el entorno que enviará la solicitud. No valides con una biblioteca y ejecutes con otra que acepte un conjunto distinto de cadenas.

Una regla útil es sencilla: solo un nombre de host analizado puede decidir a qué lugar se permite ir una solicitud. El texto de la URL sin procesar es evidencia, no autoridad.

## Userinfo crea un problema de revisión antes de crear uno de red

El cliente de red normalmente sabe adónde va. El fallo ocurre antes, cuando una persona o un control aprueba una representación equivocada de ese destino.

Un flujo típico de agente tiene varios lugares donde puede filtrarse texto sin procesar: el argumento de la llamada a una herramienta, una tarjeta de aprobación, un diario de sesión, un mensaje de error y una notificación. Si una de esas vistas dice `Calling api.example.com` porque extrae el texto anterior a `@`, está comunicando algo falso al operador. Si otra registra la cadena completa pero la corta a una anchura fija, el host real puede desaparecer por completo.

Esto importa incluso cuando el agente no tiene credenciales para el destino. Una solicitud saliente puede llevar datos de usuario, un cuerpo de solicitud firmado, un token bearer elegido por una regla amplia o simplemente alcanzar una dirección interna que nunca debería recibir tráfico de agentes. La historia de las credenciales recibe atención porque es concreta. La integridad del destino exige la misma disciplina.

La distinción que suele confundirse es la seguridad al mostrar URL frente a la seguridad al transportarla. Escapar `@` en una vista HTML puede hacer que una página resulte menos confusa, pero no determina dónde se conecta un cliente HTTP. A la inversa, un analizador puede hacer correctamente la llamada de red mientras una vista de aprobación mal diseñada sigue animando a alguien a aprobar el host equivocado. Necesitas una decisión de transporte correcta y una presentación honesta.

No sustituyas `@` por un carácter inocuo en la solicitud almacenada y sigas adelante. Eso oculta la entrada que provocó la denegación y dificulta la investigación posterior. Conserva la cadena original como entrada sin procesar, marca la solicitud como rechazada y registra el motivo analizado sin escribir credenciales integradas en un diario legible.

La vista debe empezar por un campo de destino independiente, como `Host: collector.invalid`, y colocar debajo la URL original. Ese orden convierte un acertijo de puntuación en una afirmación directa. También da al revisor un campo estable que comparar con la credencial solicitada o la integración prevista.

## Rechazar userinfo es más seguro que repararlo

Para una puerta de enlace de acciones de agentes, el valor predeterminado sensato es rechazar toda URL HTTP saliente cuyo campo de nombre de usuario o contraseña analizado no esté vacío. La mayoría de integraciones de API ya envían las credenciales en encabezados de solicitud o usan un inyector de credenciales. Admitir userinfo de URL amplía la superficie de ataque sin resolver una necesidad habitual de API.

El orden de validación importa. Analiza primero la cadena original. Rechaza URL malformadas, esquemas no admitidos y userinfo antes de aplicar listas de permitidos de hosts, mostrar avisos de aprobación, seguir redirecciones, resolver DNS o elegir credenciales. Esa secuencia evita mostrar una solicitud como apta cuando más tarde descartarás una parte de ella.

Este pseudocódigo expresa la regla:

```text
u = parse_absolute_url(raw_url)

if u.scheme not in {"https", "http"}:
    deny("unsupported scheme")

if u.username != "" or u.password != "":
    deny("URL userinfo is not accepted")

host = normalize_hostname(u.hostname)
if host == "":
    deny("missing hostname")

if not destination_is_allowed(u.scheme, host, u.port):
    deny("destination is not allowed")

send(u)
```

El analizador debe devolver campos estructurados. Dividir por `@`, quitar un prefijo o buscar una subcadena de nombre de host fallan ante variaciones normales. Una autoridad puede contener más de un `@` en la entrada sin procesar. Un analizador decide qué delimitador es sintáctico y si los caracteres anteriores pertenecen a userinfo. También trata de forma más coherente los literales IPv6 entre corchetes, puertos explícitos, codificación porcentual y componentes vacíos que una lógica de cadenas escrita a mano.

Rechazar userinfo da al solicitante una corrección clara: usa `https://api.example.com/path` y proporciona después la autenticación HTTP a través de la vía de credenciales designada. No deja al solicitante adivinando si cambiaste silenciosamente `https://name@host` por `https://host`.

Hay un caso de compatibilidad que conviene reconocer. Algunas URL antiguas integran autenticación Basic como `https://name:secret@host/path`. Si una migración debe gestionar esas URL, hazlo en una ruta de importación de una sola vez que extraiga las credenciales a almacenamiento protegido, confirme el host analizado y elimine la cadena de origen del registro de importación cuando la política lo permita. No permitas que la API de acciones en tiempo de ejecución las acepte para siempre. El código de compatibilidad temporal tiende a convertirse en superficie de ataque permanente.

## Las listas de permitidos de hosts necesitan etiquetas analizadas, no cadenas de aspecto amigable

Una lista de permitidos de destinos debe comparar nombres de host analizados y normalizados, no buscar subcadenas en la URL sin procesar. La regla `raw_url.includes("api.example.com")` acepta tanto `https://api.example.com@evil.invalid` como `https://api.example.com.evil.invalid`. Ninguna es una solicitud a `api.example.com`.

La coincidencia exacta de host es la regla menos sorprendente. Si una integración necesita solo `api.example.com`, permite ese nombre y rechaza cualquier otro. Si realmente necesita subdominios, compara etiquetas DNS: permite `example.com` y los nombres que terminan en `.example.com`, pero rechaza `badexample.com` y `example.com.evil.invalid`.

Una implementación clara tiene esta forma:

```text
function allowedHost(host, root) {
  const h = host.toLowerCase().replace(/\.$/, "")
  const r = root.toLowerCase().replace(/\.$/, "")
  return h === r || h.endsWith("." + r)
}
```

Ese código asume que el analizador de URL ya ha proporcionado un nombre de host y que quien llama ya ha rechazado userinfo. No debe recibir una URL completa. Mantener estas responsabilidades separadas impide que otro llamador pase más tarde una cadena de autoridad que contenga un puerto, un nombre de usuario o un signo `@`.

Los dominios internacionalizados también requieren una decisión. Los navegadores suelen serializar nombres de host en ASCII mediante procesamiento IDNA, mientras una persona puede ver texto Unicode. Usa para la comparación la misma forma canónica que usa tu cliente de solicitudes y muestra tanto el host canónico como una forma legible cuando difieran. No afirmes que dos cadenas identifican el mismo dominio porque se parecen con una fuente proporcional.

Las direcciones IP merecen una regla propia. Una lista de permitidos de nombres de host no convierte automáticamente en seguro un literal IP, y la resolución DNS posterior a la aprobación puede cambiar la dirección a la que llega un nombre de host. Si tu modelo de amenazas incluye acceso a servicios locales, decide explícitamente si se permiten direcciones privadas, de bucle local, enlace local y locales IPv6. Rechazar userinfo es necesario, pero por sí solo no resuelve la falsificación de solicitudes del lado del servidor.

Los puertos también tienen significado. `https://api.example.com:8443` puede ser un endpoint válido de un socio o un servicio de administración inesperado. Registra el puerto efectivo e inclúyelo en las decisiones de aprobación cuando la integración restrinja alguno. Una etiqueta de host por sí sola no describe todo el destino de red.

## Las redirecciones deben pasar por la misma puerta

Una URL inicial permitida no hace que todos los destinos de redirección estén permitidos. Las redirecciones HTTP son nuevas instrucciones de destino proporcionadas por el servidor remoto, y una puerta de enlace de agentes debe analizar y autorizar cada una antes de seguirla.

Supón que un agente solicita `https://api.example.com/export`. Ese host devuelve una respuesta 302 con esta ubicación:

```text
https://api.example.com@receiver.invalid/download?id=42
```

Un cliente que siga redirecciones automáticamente se conectará después a `receiver.invalid`. Si la puerta de enlace aprobó solo la primera URL, su lista de permitidos y su pantalla de aprobación ya no describen la acción de red que ocurrió.

Gestiona las redirecciones como un bucle con un límite elegido para tu cliente. Para cada valor de ubicación, resuelve una referencia relativa respecto a la URL actual aprobada, analiza la URL absoluta resultante, aplica las mismas reglas de esquema, userinfo, host, puerto y dirección, y decide si continuar. Registra tanto la respuesta de origen como el destino de redirección analizado.

No reenvíes credenciales entre hosts de forma predeterminada. Las bibliotecas de clientes HTTP difieren en si conservan un encabezado `Authorization` tras una redirección entre hosts, y los encabezados personalizados pueden comportarse de manera diferente. El comportamiento más seguro de una puerta de enlace asocia una credencial a un destino aprobado concreto y construye una nueva solicitud saliente solo después de que el destino de redirección pase la autorización. Una redirección entre dos hosts del mismo proveedor puede ser esperada, pero debe ser una regla explícita y no un accidente de los valores predeterminados de la biblioteca.

El tratamiento de los métodos también requiere atención. Una respuesta 303 suele cambiar una solicitud posterior a GET, mientras 307 y 308 conservan el método y el cuerpo. Si un agente publica un cuerpo sensible, una redirección que conserva el método puede enviarlo a otro lugar. Registra el método usado en cada salto y muestra el destino final en el resultado de la acción.

Un cliente también puede configurarse para no seguir redirecciones. Es una opción sensata para herramientas de API limitadas. Devuelve la respuesta de redirección al agente y exige que solicite explícitamente el siguiente destino. Esto genera otro evento de aprobación, pero da al operador un punto claro para evaluar un cambio de host. Para admitir HTTP de forma amplia, las redirecciones automáticas solo son aceptables cuando cada salto cruza la misma puerta.

## Las credenciales deben seleccionarse después de validar el destino

El orden peligroso es fácil de describir: elegir una credencial porque la URL sin procesar contiene un nombre de servicio conocido, analizar después la URL y enviar la solicitud. Un truco con `@` puede convertir el texto familiar en userinfo mientras el secreto seleccionado viaja a un host controlado por un atacante.

El orden más seguro es igual de sencillo. Primero analiza y valida la URL. Después autoriza su esquema, host, puerto y cualquier estado de redirección. Solo entonces busca una credencial vinculada a ese destino aprobado e insértala en la solicitud saliente. Mantén ese secreto fuera de los argumentos de herramientas, la memoria del agente y los valores devueltos.

Esto también resuelve un error de configuración menos llamativo, pero común. Una credencial asociada a `api.example.com` no debe enviarse automáticamente a `uploads.example.com`, aunque ambos nombres estén bajo el mismo dominio principal. Los distintos hosts suelen tener diferente propiedad, terminación TLS, registro o ámbitos de permisos. Empieza con una vinculación exacta de host. Amplía la coincidencia solo cuando la integración documente por qué lo necesita.

Insertar encabezados es preferible a usar userinfo de URL porque separa el destino de la autenticación. Un registro de solicitud puede indicar que se insertó un encabezado de autorización sin guardar su valor. El agente recibe la respuesta necesaria para continuar su trabajo, no un secreto reutilizable.

Sallyport mantiene esa separación al guardar credenciales de API y SSH en su bóveda cifrada y ejecutar la acción saliente sin exponer esas credenciales al agente. En cualquier puerta de enlace con ese modelo, rechazar userinfo antes de buscar credenciales cierra la brecha entre el destino previsto por el operador y el host que recibe la solicitud.

Tampoco confíes en un encabezado de solicitud proporcionado por el agente para identificar su destino. El encabezado `Host`, la autoridad HTTP/2, el host de la URL, la configuración del proxy y el nombre de servidor TLS pueden interactuar de formas específicas de cada cliente. Una puerta de enlace debe controlar los ajustes de conexión y derivarlos de la URL analizada y validada. Si permites encabezados personalizados, trátalos como contenido de la solicitud, no como permiso para reescribir el enrutamiento.

## Las tarjetas de aprobación deben mostrar primero el destino analizado

Una tarjeta de aprobación debe responder tres preguntas concretas sin pedir a quien la lee que reconstruya una URL: qué proceso lo solicitó, qué operación se ejecutará y qué host la recibirá. Coloca el nombre de host y el puerto analizados en una línea de destino dedicada. Pon cerca el método HTTP y la ruta. Muestra la URL original como evidencia de apoyo, no como la única señal de destino.

Para la solicitud de aspecto malformado usada antes, una tarjeta útil diría:

```text
Process: signed agent process
Action:  POST /v1/charges
Host:    collector.invalid:443
Result:  blocked because URL userinfo is present
Input:   https://api.example.com@collector.invalid/v1/charges
```

No debe mostrar `api.example.com` como una insignia derivada del lado izquierdo de la autoridad. Tampoco debe decir solo `External HTTP request`, que no da a nadie una base útil para decidir.

La aprobación por sesión y la aprobación por llamada resuelven problemas humanos distintos. Una aprobación de sesión indica que un proceso concreto en ejecución puede usar una puerta de enlace durante toda su vida. Una aprobación por llamada indica que una credencial o acción especialmente sensible necesita una nueva decisión humana. Ninguna sustituye la validación básica de URL. Un proceso en el que confías puede seguir siendo manipulado por un comentario de incidencia no confiable, un campo de metadatos de paquete o un archivo de configuración generado.

La escalera de decisiones de Sallyport mantiene una bóveda bloqueada como un bloqueo absoluto y después usa autorización de sesión y confirmación opcional por secreto. Esta estructura funciona mejor cuando los destinos malformados fallan antes de llegar a una tarjeta de aprobación, porque un operador no debería tener que decidir si un signo de puntuación de URL cambió el endpoint.

Sé específico en los mensajes de denegación. `Userinfo is not allowed in outbound URLs` indica al desarrollador del agente qué debe corregir. `Invalid request` provoca reintentos, escapes improvisados y presión para debilitar la validación. Evita repetir una contraseña si el analizador extrajo una. El mensaje puede nombrar el componente prohibido sin reproducir su contenido.

## Las pruebas deben usar entradas engañosas, no solo URL correctas

Una suite de pruebas de validación necesita ejemplos diseñados para engañar a lectores humanos y comprobaciones de cadenas simplistas. Aprobar `https://api.example.com/v1` demuestra muy poco sobre el límite en el que un agente puede enviar texto arbitrario.

Empieza con casos como estos y comprueba el host analizado, la decisión y el motivo:

```text
ALLOW  https://api.example.com/v1                 host=api.example.com
DENY   https://api.example.com@evil.invalid/v1    reason=userinfo
DENY   https://name:secret@api.example.com/v1     reason=userinfo
DENY   https://api.example.com.evil.invalid/v1    reason=host
DENY   https://api.example.com:444/v1             reason=port
DENY   https://[::1]/v1                           reason=address
```

Añade una prueba de redirección. Haz que un servidor de pruebas permitido emita una redirección cuya ubicación contenga userinfo y comprueba que el cliente registra un segundo salto bloqueado sin enviar una solicitud al servidor de destino. Así detectas el error frecuente en el que la validación de la URL inicial vive en una ruta de código y el manejo de redirecciones está dentro de una devolución de llamada de la biblioteca.

Prueba la codificación porcentual de forma deliberada, pero no supongas que cada `@` codificado equivale al carácter real. En muchos analizadores, `%40` en una ruta sigue siendo datos de ruta, mientras que un `@` real en la autoridad actúa como delimitador. Introduce la cadena sin procesar exacta en el analizador que usas en producción y comprueba sus campos. Lo importante no es una regla casera de descodificación. Es que ningún campo userinfo analizado y no vacío pueda llegar al emisor.

Prueba también el renderizado de registros y aprobaciones. Un control de seguridad puede denegar correctamente una solicitud y aun así crear un mal registro operativo si la vista corta el host real o expone texto de contraseña analizado. Merece la pena tener pruebas de instantáneas para las líneas de destino, porque las regresiones visuales suelen llegar mediante cambios de diseño que parecen inocuos.

Por último, prueba la cadena de auditoría y la ruta de revocación alrededor de una llamada denegada. Una denegación debe ser suficientemente visible para investigar, pero nunca debe incluir secretos insertados. El registro útil contiene la solicitud sin procesar bajo controles de acceso adecuados, el esquema y host analizados, el motivo de la decisión, la identidad del proceso que llama y el hecho de que no se realizó ninguna acción saliente.

## La sintaxis de URL no es un lenguaje de políticas

Algunos equipos responden a los casos límite de URL añadiendo una pila creciente de excepciones: permitir un nombre de usuario para este proveedor, aceptar un puerto especial para ese entorno, confiar en una redirección solo si un encabezado parece correcto y parchear un comparador de cadenas para cada incidente. Este enfoque parece flexible porque evita decir no a una solicitud extraña. También crea reglas que nadie puede revisar de forma fiable.

Mantén la regla pequeña. Las solicitudes salientes tienen un destino analizado. Userinfo se rechaza. El esquema, host, puerto y clase de dirección permitidos son explícitos. Las redirecciones vuelven a pasar por las mismas comprobaciones. Las credenciales se seleccionan solo después de que el destino sea aprobado. Cada decisión genera un registro que expresa claramente el host analizado.

Esa regla rechazará algunas URL antiguas que un navegador podría aceptar. Bien. Un agente autónomo no necesita todas las concesiones históricas de la barra de direcciones de un navegador. Necesita una interfaz limitada que haga difícil confundir la relación entre acción, destino y credencial.

Si necesitas cambiar esa interfaz, haz visible la excepción como una capacidad con nombre, pruebas y una decisión de caducidad. No la escondas en código de limpieza de URL. La primera entrada hostil encontrará la diferencia entre el texto que parece un host y el host al que realmente se conecta tu cliente.
