¿Se aplican realmente los límites de credenciales de los subdominios API?
Los límites de credenciales de los subdominios API impiden que los tokens lleguen a hosts hermanos, regionales, de vanidad y de tenants mediante redirecciones o código de cliente permisivo.

Un nombre de host no indica propiedad. Forma parte de la identidad del destino que decide adónde va una credencial API. Si un cliente tiene un único token bearer y trata api.example.com, files.example.com, api.eu.example.com y customer.example.com como equivalentes porque comparten un sufijo, el cliente ya tomó la decisión peligrosa.
Este error se esconde detrás de una arquitectura aparentemente amigable. Una empresa puede ser propietaria de todos los nombres bajo un dominio principal. DNS puede enviar varios nombres al mismo balanceador. Los certificados pueden cubrirlos todos. Nada de eso significa que una credencial emitida para una API deba llegar a otro nombre de host. Las credenciales necesitan una regla de destino explícita, y esa regla necesita pruebas que fallen si alguien la amplía más adelante.
Los nombres de host no heredan confianza del dominio principal
api.example.com y admin.example.com pueden compartir un dominio registrable, pero son orígenes distintos. Un origen incluye el esquema, el host y el puerto. https://api.example.com y https://api.example.com:8443 también son orígenes distintos. La diferencia importa porque el cliente elige un destino de red mediante esa autoridad, no por la relación comercial entre los servicios.
RFC 9110 define un espacio de protección de autenticación HTTP mediante el origen y, cuando existe, un ámbito. También advierte que depender solo de los ámbitos puede exponer credenciales a otros recursos del mismo origen, y recomienda usar nombres de host o puertos separados cuando distintas partes necesitan separación. Es una advertencia útil, pero no permite tratar todos los nombres bajo un mismo sufijo como un único espacio de protección.
Los equipos suelen mezclar tres ideas diferentes:
- Un dominio principal nombra un espacio de nombres DNS.
- Un sitio agrupa orígenes web relacionados para determinadas reglas de seguridad del navegador.
- Un origen identifica un esquema, host y puerto concretos como destino HTTP.
Solo las dos primeras hacen que los subdominios parezcan relacionados. El selector de credenciales de tu API debe usar la tercera.
Esto importa incluso cuando el emisor de la credencial incluye una afirmación aud amplia en el token. Una afirmación de audiencia amplia indica lo que el servicio receptor puede aceptar. No indica a tu cliente que ofrezca el token a todos los endpoints que podrían aceptarlo. El emisor sigue teniendo la obligación de limitar la distribución.
He visto este fallo muchas veces. Un equipo empieza con un único endpoint, añade api.staging.example.com y conserva un asistente llamado getApiKey(). Seis meses después, el asistente está debajo de cinco servicios. No tiene argumento de host, ni lista de permitidos, ni forma de saber por qué debería rechazar una llamada. La clave no se volvió insegura por un exploit exótico. Se volvió insegura porque el código dejó de conservar el hecho de que la clave pertenecía a un destino concreto.
Haz visible la vinculación con el host en la configuración y en las revisiones de código. Un selector de credenciales que solo acepta el nombre de una credencial ya carece de la mitad de sus datos de entrada.
Las cuatro formas de nombre de host fallan de maneras distintas
Los hosts hermanos, de vanidad, regionales y específicos de clientes merecen casos de prueba separados porque fallan por motivos diferentes. Una afirmación vaga de que el token se mantiene dentro de «nuestros dominios» pasará por alto al menos uno de ellos.
Un host hermano está junto al host previsto: metrics.example.com en lugar de api.example.com. A menudo expone una entrada compartida, un servicio antiguo o una herramienta interna que nunca debió recibir credenciales de producción. Es especialmente peligroso cuando un proxy genérico reenvía las cabeceras sin cambios a un backend elegido por la configuración de rutas.
Un host de vanidad es un alias fácil de recordar, como api.brand.example o developer.example.com. Los proveedores crean estos nombres durante cambios de marca, adquisiciones o migraciones. El alias puede terminar en otro punto de acceso, usar telemetría diferente o redirigir al host canónico. No le concedas acceso a credenciales solo por haber visto una redirección en un navegador.
Un host regional cambia la ubicación y a menudo también la propiedad del servicio: api.us.example.com, api.eu.example.com o api.ap-southeast.example.com. La solicitud puede transportar datos de negocio antes de que la aplicación rechace el token. Una respuesta 401 no demuestra que enviar la credencial y el cuerpo a esa región haya sido aceptable.
Un host específico de cliente agrava los errores multi-tenant: acme.vendor.example y northwind.vendor.example pueden resolverse mediante la misma infraestructura, pero cada nombre representa el límite de un tenant. Un token suficientemente amplio para funcionar en ambos puede ser intencionado para un plano de control central. Nunca debe convertirse en la credencial predeterminada para solicitudes dirigidas a tenants.
Incluye cada categoría en tu inventario. No las agrupes en un campo llamado allowed_domains suponiendo que un comodín simplifica la política. El comodín oculta la decisión difícil.
Vincula la selección de credenciales con la autoridad completa
Un cliente seguro asigna una autoridad de solicitud a un único registro de credenciales y rechaza la solicitud cuando no existe ninguna coincidencia. La autoridad completa incluye el esquema, el nombre de host normalizado y el puerto efectivo. Para una API HTTPS normal en el puerto 443, la configuración visible puede seguir siendo sencilla, pero la implementación debe rechazar un puerto inesperado en lugar de reutilizar el token en silencio.
Un inventario viable puede ser así:
credentials:
billing-production:
allowed:
- https://api.billing.example.com:443
header: Authorization
scheme: Bearer
telemetry-eu:
allowed:
- https://ingest.eu.example.net:443
header: X-Write-Key
El punto no es YAML. El nombre del token no debe existir por sí solo. billing-production tiene un destino explícito y telemetry-eu no puede filtrarse a un endpoint de Estados Unidos porque alguien cambió únicamente una cadena del nombre de host.
Evita este patrón:
const headers = {
Authorization: `Bearer ${process.env.PRODUCTION_API_TOKEN}`
};
await fetch(userSuppliedUrl, { headers });
El código ha unido un destino sin restricciones a una credencial con privilegios elevados. A veces los desarrolladores lo defienden diciendo que la URL proviene de un archivo de configuración confiable. Ese archivo sigue siendo un límite de entrada. Las herramientas de despliegue, las sustituciones de entorno, las solicitudes de cambios, los indicadores de funcionalidades y un proceso de compilación comprometido pueden modificarlo.
Usa un constructor de solicitudes que se niegue a crear solicitudes autenticadas hasta encontrar un destino normalizado. Mantén la normalización del host sencilla y estricta:
- Analiza la URL con un analizador real de URL, no con comprobaciones de sufijos de cadenas.
- Exige
https:, salvo que exista una excepción documentada para desarrollo local. - Convierte el host a minúsculas y canonicalízalo mediante el analizador.
- Compara el esquema, host y puerto resultantes con autoridades aprobadas exactas.
- Añade la cabecera de autenticación solo después de que la coincidencia tenga éxito.
No escribas host.endsWith("example.com"). Acepta notexample.com. Tampoco lo arregles con endsWith(".example.com") y des por terminado el trabajo. Esa expresión sigue entregando el token a todos los subdominios actuales y futuros, incluidos los nombres delegados a un cliente, un proveedor o un entorno de desarrollo olvidado.
La diferencia entre autorizar el destino y autorizar la credencial debe mantenerse clara. Una API receptora puede rechazar un token fuera de alcance. Tu emisor debe impedir que el token salga hacia esa API. El primer control limita la exposición. El segundo decide el acceso. Necesitas ambos.
Una redirección es un destino nuevo, no la continuación de la solicitud anterior
La gestión de redirecciones crea un segundo límite de credenciales. El primer host puede estar aprobado y después devolver una cabecera Location que apunte a un nombre de vanidad, un endpoint regional, un almacenamiento de objetos o un host que un atacante controle mediante una redirección abierta. Si el cliente sigue la redirección automáticamente, debe volver a autorizar el destino antes de enviar cualquier credencial o cuerpo de solicitud.
libcurl deja esta diferencia especialmente clara. De forma predeterminada, no envía cabeceras de autenticación generadas internamente ni cabeceras de cookies establecidas explícitamente a otro host durante las redirecciones. Su opción CURLOPT_UNRESTRICTED_AUTH cambia ese comportamiento y puede enviar credenciales a hosts seleccionados por las respuestas de redirección. El proyecto curl advierte que las cabeceras personalizadas requieren atención separada, porque la biblioteca no puede deducir qué cabeceras arbitrarias contienen secretos.
Este último detalle sorprende incluso a equipos con experiencia. Prueban la autenticación Basic con un cliente estándar y ven que una redirección entre hosts se comporta de forma segura. Después, la integración de producción usa X-Api-Key, Authorization: Bearer o X-Signature insertadas como cabeceras genéricas. Una biblioteca puede conservar esa cabecera si la aplicación no la elimina. Una prueba correcta para un mecanismo de autenticación no dice nada sobre otro.
Trata las redirecciones según la clase de solicitud:
- Para operaciones de escritura, rechaza las redirecciones salvo que el contrato de la API las exija.
- Para operaciones de lectura, inspecciona cada destino de redirección y reconstruye las cabeceras a partir del registro de credenciales aprobado para ese destino.
- Para solicitudes firmadas, vuelve a crear la firma después de autorizar el destino final. Nunca reenvíes una firma creada para el primer host.
- Para cargas, no reenvíes la cabecera de autorización original a una URL de almacenamiento prefirmada. La firma de la consulta o los campos del formulario ya contienen la autoridad limitada que espera ese endpoint de almacenamiento.
Una prueba de redirección debe inspeccionar la solicitud que llega al segundo servidor. Comprobar solo el código de estado final no basta. Una respuesta 200 de un receptor de prueba inofensivo puede ocultar que recibió la cabecera de producción que querías proteger.
Los reintentos necesitan la misma disciplina. Algunos envoltorios HTTP reconstruyen las solicitudes a partir de un mapa de cabeceras almacenado en caché. Si un reintento sigue el descubrimiento de servicios hacia una autoridad nueva, descarta el mapa guardado y vuelve a consultar al selector de credenciales. Reutilizar cabeceras es más rápido. También es la forma en que desaparece el contexto del destino.
Crea un sistema de pruebas negativas antes de confiar en la lista de permitidos
La prueba importante es negativa: una credencial debe aparecer en el host previsto y no debe aparecer en todos los hosts equivocados plausibles. Puedes ejecutarla con dos servidores HTTPS locales, pero el receptor debe registrar únicamente la presencia y el nombre de las cabeceras sensibles. No pongas valores reales en los registros de prueba.
Para una comprobación desde el shell, asigna nombres inofensivos a servidores locales con la opción --resolve de curl y usa un token desechable. Inicia un receptor en el puerto 8443 para el host aprobado y otro en el 9443 para el hermano. Cada receptor debería emitir un registro con esta forma:
host=api.test.example
path=/v1/ping
authorization=present
x-api-key=absent
El receptor hermano debería emitir el resultado inverso:
host=metrics.test.example
path=/v1/ping
authorization=absent
x-api-key=absent
Después prueba el envoltorio HTTP real, no una versión aislada de su lógica. Una prueba con curl puede mostrar la conexión básica:
curl --silent --show-error \\
--resolve api.test.example:8443:127.0.0.1 \\
--header 'Authorization: Bearer test-token-do-not-use' \\
https://api.test.example:8443/v1/ping
Ese comando coloca deliberadamente la cabecera en la solicitud, por lo que solo demuestra lo que registra el receptor. No demuestra que tu aplicación seleccione las credenciales de forma segura. La prueba de la aplicación debe llamar a su función normal request() con la misma autoridad aprobada, repetirla con cada autoridad incorrecta y comprobar que genera un error antes de abrir una conexión.
Usa una matriz que obligue a tomar las decisiones que normalmente se dan por supuestas:
| Autoridad solicitada | Comportamiento esperado de la credencial |
|---|---|
https://api.test.example | Enviar la credencial de prueba prevista |
https://metrics.test.example | Rechazar antes de enviar |
https://api.eu.test.example | Rechazar salvo que esté configurada por separado |
https://tenant-a.test.example | Rechazar salvo que la vinculación con el tenant sea explícita |
| El host aprobado redirige al hermano | Seguir solo después de volver a autorizar, normalmente sin la credencial original |
No uses un servicio externo de bandeja de solicitudes para esta prueba. Enseñarías al equipo a enviar secretos de prueba a un tercero mientras comprueba si envía secretos a terceros. Un receptor local es sencillo y mantiene las evidencias bajo tu control.
Incluye la matriz en la integración continua. Una prueba unitaria sobre isAllowedHost() ayuda, pero una prueba de integración detecta la regresión habitual: alguien añade una cabecera predeterminada en una capa HTTP inferior después de que ya se ejecutó la comprobación del host.
Las reglas del navegador no son reglas del agente
El vocabulario de los navegadores provoca suposiciones equivocadas en el código de agentes y backends. «Mismo sitio» puede incluir subdominios, mientras que «mismo origen» no. MDN usa https://example.org y https://login.example.org como ejemplo de dos orígenes que pertenecen al mismo sitio. La distinción existe porque un subdominio comprometido puede atacar a un hermano mediante rutas del mismo sitio.
Fetch usa credentials: "same-origin" de forma predeterminada, lo que impide que fetch del navegador incluya automáticamente credenciales en solicitudes entre orígenes. Un desarrollador puede ver ese valor, probar una llamada desde el frontend y concluir que un token bearer no puede pasar de un subdominio a otro. Esa conclusión no se sostiene en el código del servidor. Un envoltorio fetch del backend puede añadir cualquier cabecera que le entregue su autor. Una CLI puede hacer lo mismo. Un agente autónomo puede invocar una herramienta HTTP genérica con una URL y cabeceras, salvo que la herramienta mantenga la credencial fuera de su alcance.
CORS no corrige esto. CORS controla principalmente si el JavaScript del navegador puede leer una respuesta. No convierte un inyector amplio de cabeceras del servidor en un selector seguro de credenciales. En algunas solicitudes del navegador, el navegador puede enviar la credencial y después negarse a exponer la respuesta al script. Eso no es un control aceptable para evitar la pérdida de datos.
Las cookies añaden otra fuente de confusión. Los atributos de dominio de una cookie pueden permitir que llegue a los subdominios, mientras que las cookies restringidas al host no. Las cabeceras bearer no tienen un alcance de dominio integrado comparable. Si tu cliente añade Authorization, ha tomado una decisión explícita para esa solicitud. No uses el modelo mental de las cookies para las claves API.
Los dominios de clientes necesitan un límite del emisor, no una convención de nombres
Una API central puede llamar legítimamente a muchos endpoints de clientes, pero necesita credenciales que indiquen por qué la llamada puede cruzar límites entre tenants. La configuración más segura asigna a cada host de cliente un registro de credenciales separado y de alcance reducido. La siguiente mejor opción usa un token de corta duración cuyas afirmaciones de audiencia y tenant sean verificadas por el destino, junto con una lista del cliente que siga nombrando cada host permitido.
Un token global con un rol amplio es popular porque facilita el alta. Se añade un tenant, se apunta la integración a su subdominio y la llamada funciona. La misma comodidad permite que un error tipográfico, un cambio malicioso de configuración o un agente confundido llegue al servicio de otro cliente con una credencial sin un límite de destino significativo.
No lo soluciones aprobando *.customers.example.com y diciéndote que cada coincidencia corresponde a un cliente. Pregunta quién puede crear esos nombres, quién puede delegar DNS, qué hosts dirigen a entornos de vista previa y si los nombres persisten después de dar de baja a un cliente. Un comodín convierte todas esas preguntas en decisiones de seguridad, normalmente sin dejar un registro de revisión.
Hay casos en los que un comodín controlado es apropiado. Un proveedor puede emitir tokens por tenant cuando el token incluye un identificador de tenant, el servicio rechaza las discrepancias y un registro interno verifica el host del tenant antes de que el cliente envíe la solicitud. En ese diseño, el comodín no es la regla de autorización. Es una comodidad limitada detrás de un registro de tenants con autoridad. Si no puedes identificar ese registro y probar cómo falla, usa entradas exactas.
El DNS privado crea el mismo problema dentro de una empresa. payments.prod.internal y payments.dev.internal quizá no sean nombres públicos, pero son destinos distintos con controles operativos distintos. El DNS interno no sustituye al alcance de las credenciales.
Las comprobaciones del host deben hacerse antes del descubrimiento y después de la normalización
El descubrimiento de servicios, el enrutamiento personalizado y la configuración del proxy pueden eludir silenciosamente una lista de permitidos que parecía correcta. Si el código comprueba la URL original y después un resolvedor sustituye el destino interno, la aplicación puede enviar una credencial a otra autoridad. Al contrario, una comprobación que use una cabecera Host modificable puede aprobar una solicitud cuya conexión de red termina en otro lugar.
Usa la autoridad de la URL analizada como entrada de la política. Establece la conexión TLS para esa autoridad y verifica el certificado con normalidad. No desactives la verificación de certificados para que funcione una ruta interna o un accesorio de prueba. La propia guía de seguridad de curl indica que un cliente que no puede autenticar al par no puede saber si llegó al servidor previsto.
Después decide qué dice tu modelo de confianza sobre los proxies. Un proxy de reenvío es una elección de transporte, no un nuevo destino de credenciales, si el cliente establece TLS con el origen aprobado a través de él. Un proxy inverso que termina TLS forma parte del límite del servicio y necesita el mismo nivel de revisión que la propia API. Un proxy HTTP que recibe cabeceras de autorización sin cifrar tiene acceso a las credenciales. No llames a ese detalle «solo infraestructura».
Normaliza los nombres de host internacionalizados mediante una implementación de URL compatible con los estándares y compara el resultado canónico. Rechaza la información de usuario en URL como https://[email protected]/; las credenciales incluidas en las URL se filtran a registros, historiales y salidas de depuración. Rechaza los fragmentos en solicitudes HTTP y decide explícitamente si las cadenas de consulta pueden contener credenciales prefirmadas. Una función de saneamiento genérica no puede rescatar un diseño que acepta cualquier URL y espera identificar las malas después.
El orden importante es sencillo: analizar, normalizar, autorizar el destino, seleccionar la credencial, construir las cabeceras y conectar. Si una operación posterior cambia la autoridad, vuelve a empezar por la autorización del destino.
Audita la decisión, no el secreto
Un registro de auditoría debe demostrar qué destino consideró el cliente y qué regla de credenciales seleccionó, sin registrar el secreto. Necesitas ese registro cuando alguien pregunta si un token pudo llegar a un host hermano después de un cambio de despliegue.
Guarda campos como el ID de solicitud, la hora, la autoridad normalizada, el ID del registro de credenciales, el resultado de la autorización, el origen y el destino de las redirecciones y un código de resultado. Aplica hash u oculta las rutas si contienen identificadores de clientes. No registres Authorization, las cabeceras de claves personalizadas, las cadenas de consulta que contengan firmas ni los cuerpos completos de las solicitudes solo porque ayuden a depurar.
Una buena entrada de auditoría responde a una pregunta concreta:
request_id=01J...
authority=https://api.billing.example.com:443
credential=billing-production
destination_check=allowed
redirect_count=0
result=201
Para una solicitud rechazada al host hermano, el registro debe mostrar credential=none y destination_check=denied. Esa diferencia demuestra que el cliente rechazó la solicitud antes de seleccionar un secreto. Si el registro muestra un nombre de credencial y después informa de un 403 del host equivocado, el sistema ya envió más de lo debido.
Sallyport conserva el secreto en su bóveda cifrada y evalúa una acción antes de que el agente reciba cualquier material de credenciales. Sus registros Activity y Sessions permiten inspeccionar el recorrido de la acción y revocar una sesión de agente activa cuando una solicitud vinculada a un host sale mal.
Empieza por el inventario. Para cada credencial, escribe una autoridad prevista y después nombra un host hermano, uno de vanidad o migración, uno regional y uno específico de cliente que no deban recibirla. Si tu cliente no puede hacer hoy esas afirmaciones negativas, no tiene un límite de credenciales. Tiene una convención basada en la esperanza.
FAQ
¿Las claves API funcionan automáticamente entre subdominios?
No. api.example.com y billing.example.com son orígenes distintos, aunque ambos nombres estén gestionados por el mismo equipo. Trata la vinculación de una credencial como exacta, salvo que su emisor documente explícitamente una audiencia más amplia y hayas probado ese alcance.
¿Es seguro enviar un mismo token bearer a todos los subdominios?
Solo deben recibir el token si tu cliente está configurado deliberadamente para enviarlo allí. Un dominio principal compartido demuestra la propiedad de un espacio de nombres, no la autorización para reutilizar la credencial de un servicio en otro.
¿Una redirección HTTP puede filtrar una credencial API a otro host?
Una redirección puede cambiar el destino después de la primera solicitud, por lo que debe tener su propia comprobación del host. Nunca supongas que un cliente HTTP elimina una cabecera de credenciales personalizada solo porque elimina la autenticación HTTP integrada.
¿Cada subdominio de cliente debe usar una credencial independiente?
No. Un host como tenant-a.api.example.com normalmente debería tener una credencial vinculada a ese tenant o un token con una afirmación de tenant que el servicio haga cumplir. Una clave de administrador general dirigida a hosts de tenants convierte un error de enrutamiento en un incidente entre tenants.
¿Los endpoints de API regionales necesitan credenciales distintas?
Sí, si el endpoint regional tiene un nombre de host diferente. Que dos servicios regionales pertenezcan al mismo proveedor no hace que sus nombres sean intercambiables, y un error de región puede enviar datos al límite de residencia equivocado antes de que falle la autenticación.
¿Los dominios de vanidad para API son seguros con credenciales de producción?
Los nombres de vanidad son arriesgados porque parecen inofensivos y a menudo se sitúan delante de sistemas de enrutamiento, registros u organizaciones distintas. Vincula las credenciales al host canónico del servicio, salvo que el proveedor documente el nombre de vanidad como una audiencia autenticada equivalente.
¿Cómo compruebo si mi cliente envía un token a dominios hermanos?
Registra la URL exacta, la credencial seleccionada, el comportamiento de las redirecciones y si la solicitud llevaba una cabecera de autenticación. La prueba más útil ejecuta un endpoint inocuo contra hosts aprobados y hosts deliberadamente no aprobados, y falla si la credencial aparece en un lugar donde no debería.
¿CORS impide que las credenciales lleguen a un subdominio?
CORS controla lo que puede leer el JavaScript del navegador. Las cookies tienen sus propias reglas de dominio y SameSite. Un agente de servidor, una CLI o un cliente HTTP personalizado puede enviar cualquier cabecera a cualquier host, salvo que su código detenga la solicitud.
¿Debo usar listas de hosts con comodines para las credenciales API?
Para tokens bearer, cabeceras de claves API personalizadas y destinos SSH, normalmente conviene usar listas de hosts exactas. Usa un comodín restringido solo cuando el sistema de credenciales demuestre la audiencia del tenant o del servicio seleccionado y puedas verificar continuamente qué hosts concretos coincidieron.
¿Cuál es la primera solución para un error en los límites de credenciales?
La primera medida es inventariar todas las vinculaciones entre credenciales y hosts y eliminar los valores predeterminados amplios, como *.example.com. Después añade una prueba negativa para cada credencial: un destino aprobado debe recibirla, pero los destinos hermanos, de vanidad, regionales y de tenants no.