Las cabeceras reenviadas no son entradas de confianza: X-Forwarded-For, Proto y Host tras un túnel
Detrás de un túnel, X-Forwarded-For, Proto y Host son entradas no confiables. Aprende las configuraciones correctas de trustProxy para Express, Fastify, Django y Rails.

Quick answer
Las cabeceras reenviadas no son entradas de confianza detrás de un túnel: quick answer
If free tunnel limits interrupt your workflow, compare session length, stable URLs, concurrent tunnels, and paid-plan pricing before choosing a localhost tunnel tool.
What free tunnel limits should developers check first?
Check session duration, URL stability, concurrent tunnels, custom subdomains, bandwidth or request limits, and whether webhook callbacks survive restarts.
How does InstaTunnel handle longer development sessions?
InstaTunnel Free is designed around 24-hour sessions, with Pro available for higher limits and MCP endpoint tunnel workflows.
Cuando colocas una aplicación detrás de un túnel, tres cabeceras de solicitud empiezan a decidir lo que tu código crea. X-Forwarded-For indica quién está llamando, X-Forwarded-Proto indica si la solicitud fue HTTPS, y X-Forwarded-Host indica qué nombre de host pidió el llamante. Configura trustProxy de forma incorrecta y obtendrás una de dos fallos. El menos perjudicial es un bucle de redirección, o un limitador de tasa que ve a un único visitante. El más peligroso es un atacante que elige los valores en los que descansan tus verificaciones de seguridad.
2026 hizo que la segunda falla se volviera concreta. Fastify presentó dos CVEs en cinco meses precisamente por este problema, y el paquete proxy-addr detrás de Express, que alimenta req.ip, tenía un fallo crítico de confianza en la subred. Esta guía cubre lo que realmente envía un túnel, por qué las apps lo malinterpretan, y las configuraciones que funcionan en Express, Fastify, Django y Rails.
Qué envía realmente un túnel
Un túnel cambia la forma de la conexión. La sesión TLS del visitante termina en el borde del proveedor. El borde reenvía la solicitud a través del túnel hacia un agente en tu máquina, y el agente realiza una solicitud HTTP simple a tu aplicación en localhost. Tu aplicación nunca ve el socket del visitante. La conexión que ve proviene del agente, normalmente 127.0.0.1, y todo lo demás sobre el visitante llega como cabeceras.
La documentación del proveedor describe esas cabeceras:
- ngrok añade la IP del cliente a
X-Forwarded-Fory el esquema de la solicitud aX-Forwarded-Proto. Si el visitante ya envió cualquiera de esas cabeceras, ngrok añade un nuevo valor en lugar de reemplazar el existente, y su documentación te indica leer el último valor. ParaX-Forwarded-Host, ngrok mantiene la cabecera del visitante cuando existe y, de lo contrario, usa elHostde la solicitud. - Cloudflare envía la dirección del visitante en
CF-Connecting-IPy la recomienda sobreX-Forwarded-For. Añade la dirección de conexión a cualquier existenteX-Forwarded-For, de modo que los valores que el visitante haya escrito queden a la izquierda. Un problema público decloudflaredmuestra una solicitud con una lista forjada que llega al origen con la dirección real añadida al final.
El patrón es el mismo en todas partes: los bordes añaden, no sanan. Todo lo que un visitante escribió sigue estando en la cabecera que recibe tu aplicación. Solo las entradas añadidas por saltos que controlas pueden considerarse fiables.
Por qué tu limitador de tasa ve la IP del túnel
La mayoría de limitadores de tasa se basan en la dirección del socket. Detrás de un túnel, cada solicitud llega desde el agente en localhost, por lo que cada visitante cae en el mismo cubo. Un cliente ruidoso agota la cuota de todos, y una lista blanca de IPs o bien bloquea todo el tráfico o lo permite todo.
La solución es decirle a tu framework cuál dirección de conexión es un proxy de confianza, para que pueda leer la dirección del cliente desde X-Forwarded-For de forma segura. La solución incorrecta es leer tú mismo el primer valor de la cabecera. La guía de MDN es directa: cualquier uso de seguridad de X-Forwarded-For, como limitación de tasa o control de acceso por IP, debe usar solo direcciones añadidas por un proxy de confianza, y los valores de la izquierda solo deben usarse donde la suplantación no tenga impacto. Un artículo de Go que examinó limitadores de tasa encontró el mismo error repetido, con un análisis de la izquierda que permite a los clientes evadir sus límites.
Por qué una detección de protocolo incorrecta provoca bucles de redirección
TLS termina en el borde, por lo que tu aplicación recibe HTTP sin cifrar. Si fuerza HTTPS, ve una solicitud insegura y responde con una redirección a https://. El navegador sigue, la solicitud pasa por el borde de nuevo, y tu aplicación vuelve a ver HTTP sin cifrar. El bucle nunca termina.
La documentación de Django lo dice directamente: si SECURE_SSL_REDIRECT provoca redirecciones infinitas, probablemente el sitio no pueda distinguir qué solicitudes son seguras detrás de un proxy, y SECURE_PROXY_SSL_HEADER es la solución. Rails documenta la misma trampa, señalando que detrás de un proxy que termina SSL, la solicitud reenviada parece HTTP, lo que envía redirecciones y seguridad de cookies al esquema incorrecto.
Antes de cambiar cualquier configuración, imprime lo que realmente llega. Un artículo de 2024 encontró que X-Forwarded-Proto permanecía http a través de un Cloudflare Tunnel y usaba la cabecera CF-Visitor en su lugar. Los proveedores cambian el comportamiento, así que verifica con tu propia solicitud.
La regla: confía en la conexión y luego en la cabecera
Las cabeceras X-Forwarded-* son afirmaciones, no hechos. MDN afirma que si un servidor puede ser alcanzado directamente desde Internet, incluso si también se encuentra detrás de un proxy de confianza, ninguna parte de la lista X-Forwarded-For es confiable. El algoritmo seguro tiene dos pasos:
- Verifica si la dirección de conexión pertenece a un proxy de confianza.
- Y solo entonces lee la cabecera desde la derecha, omitiendo proxies de confianza. La primera dirección no confiable es el cliente.
Los frameworks que omiten el paso uno, o lo implementan mal, son los que hicieron las advertencias de 2026.
La cadena de CVEs de 2026
CVE-2026-3635 (Fastify, publicado el 23 de marzo de 2026). En Fastify 5.8.2 y versiones anteriores, un valor restrictivo de trustProxy, como una IP, una subred, un conteo de saltos o una función personalizada, aún permitía que request.protocol y request.host leyeran X-Forwarded-Proto y X-Forwarded-Host desde cualquier conexión, incluidas las no confiables. Un atacante que llegue al servidor directamente podría falsificar ambos. La advertencia enumera la aplicación de HTTPS, banderas de cookies seguras, comprobaciones de origen CSRF, construcción de URLs y enrutamiento basado en el host como afectadas. La puntuación CVSS es 6.1, y la corrección es Fastify 5.8.3. Con trustProxy: true el comportamiento es esperado, ya que esa configuración confía en todo.
CVE-2026-16732 (Fastify, publicado el 18 de agosto de 2026). La guardia de la 5.8.3 verificaba la dirección que se conecta, lo cual funciona para formas IP, CIDR y funciones. La forma numérica de conteo de saltos se compila a un predicado que ignora la dirección, así que la guardia siempre pasaba. Las versiones 5.8.3 hasta, pero sin incluir, 5.12.1 siguieron siendo vulnerables. El impacto coincide con la CVE padre y añade adulteración de caché. Fastify 5.12.1 desactiva trustProxy numérico en tiempo de ejecución y lo quita de los tipos de TypeScript. La solución propuesta por el aviso es un valor de IP, CIDR o función, además de asegurarte de que el origen sea alcanzable solo a través de la cadena de proxies.
CVE-2026-90711 (proxy-addr, publicado el 15 de septiembre de 2026). proxy-addr determina direcciones de cliente detrás de proxies de confianza y respalda req.ip y req.ips de Express. En las versiones 1.1.0 a 2.0.7, una subred de confianza escrita en forma IPv4-mapped IPv6 con un prefijo de tamaño IPv4, como ::ffff:10.0.0.0/8 en lugar de ::ffff:10.0.0.0/104, fue aceptada silenciosamente y se confiaba en cada dirección IPv4. Cualquier cliente podría entonces suministrar un arbitrario X-Forwarded-For, venciendo el control de acceso por IP, la limitación de tasa, la geolocalización y los registros de auditoría. La puntuación CVSS es 9.1, y la corrección es 2.0.8. El complemento de Fastify lo heredó como CVE-2026-92395, afectando a @fastify/proxy-addr de 3.0.0 a 5.1.0, corregido en 5.1.1.
Las advertencias comparten una precondición: el atacante debe llegar a tu app sin pasar por el proxy. En un túnel, eso suena poco probable hasta que verifiques cómo está enlazado tu servidor de desarrollo. Un servidor que escucha en 0.0.0.0, o un contenedor con un puerto publicado, es alcanzable desde tu LAN sin que exista túnel involucrado. Eso es una inferencia de cómo funciona el enlace, no algo que digan las advertencias, pero explica por qué “solo es un túnel de desarrollo” es una consuelo débil. Enlaza a 127.0.0.1.
Configuraciones de confianza correctas por framework
Express
Express por defecto establece trust proxy en false, por lo que req.ip es la dirección del socket y cada visitante parece el agente. Configurarlo a true soluciona eso y crea un problema diferente: Express trata entonces la entrada más a la izquierda de X-Forwarded-For como el cliente, y su documentación advierte que el último proxy de confianza debe sobrescribir las cabeceras de reenvío o los clientes pueden proporcionar cualquier valor. Dado que los túneles añaden, true es la elección incorrecta.
Confía en la dirección del agente en su lugar:
// Agent runs on the same machine
app.set('trust proxy', 'loopback');
// Agent in a container: trust that network only
app.set('trust proxy', ['loopback', '172.18.0.0/16']);
Express lee X-Forwarded-For desde la derecha solo cuando el par de sockets es de confianza. Con esa configuración, req.protocol sigue a X-Forwarded-Proto, req.hostname sigue a X-Forwarded-Host, y req.ip es la primera dirección no confiable desde la derecha. Actualiza proxy-addr a 2.0.8 o posterior (npm ls proxy-addr muestra qué versión tienes) y verifica doblemente cualquier subred IPv4-mapped IPv6.
Fastify
Actualiza a 5.12.1 o posterior, y deja de usar valores numéricos:
const fastify = require('fastify')({
trustProxy: ['127.0.0.1', '::1'] // IPs or CIDRs of the agent only
});
Las guías actuales de Fastify también etiquetan request.ip, request.host y request.protocol como metadatos de la solicitud para tratar como entradas no confiables. Enlaza el servidor a loopback para que la verificación no pueda eludirse.
Django
Django no tiene un equivalente de trust proxy para IPs de cliente. REMOTE_ADDR es el par de sockets, por lo que necesitas una elección deliberada. Dos configuraciones importan para el esquema:
# settings.py
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
La advertencia de Django aplica: configúralo solo cuando el proxy elimina X-Forwarded-Proto de las solicitudes entrantes, incluidas las que llegan como listas separadas por comas. Un túnel que añade en lugar de sobrescribir no cumple con ese requisito, así que verifica con la prueba de cabeceras a continuación. En la capa del servidor, gunicorn confía en cabeceras reenviadas solo desde 127.0.0.1 por defecto mediante forwarded_allow_ips, y uvicorn con --proxy-headers está activado por defecto pero limitado a --forwarded-allow-ips, que por defecto es 127.0.0.1. Las configuraciones de contenedores tentarán a las personas a usar *; lista la red del agente en su lugar. Para IPs de cliente, django-ipware admite proxy_trusted_ips y proxy_count para exactamente esto.
Rails
ActionDispatch::RemoteIp elimina proxies de confianza de la cadena reenviada y devuelve la última dirección que queda. Su lista de proxies de confianza por defecto cubre el loopback y Rangos privados, lo que conviene para un agente de túnel en localhost. También genera IpSpoofAttackError cuando Client-Ip y X-Forwarded-For no coinciden. Si configuras config.action_dispatch.trusted_proxies, pasa un arreglo, porque un enumerable reemplaza los defaults.
Para el bucle de redirección, Rails ofrece config.assume_ssl = true, que indica al servidor que el proxy ya terminó con SSL. Úsalo solo cuando la app sea alcanzable exclusivamente a través del túnel, porque la configuración trata cada solicitud como HTTPS.
Prueba como un atacante
Envía cabeceras forjadas directamente a tu puerto local, evitando el túnel:
curl -s http://127.0.0.1:3000/debug \\
-H 'X-Forwarded-For: 203.0.113.9' \\
-H 'X-Forwarded-Proto: https' \\
-H 'X-Forwarded-Host: evil.example'
Una configuración correcta reporta la dirección de socket como el cliente, HTTP sin cifrar y tu host real, porque la dirección de conexión no era un proxy de confianza. Luego solicita la misma ruta a través del túnel con un forjado X-Forwarded-For y confirma que tu aplicación reporta la dirección añadida por el borde, no la forjada. Ejecuta ambas comprobaciones en todos los entornos, ya que el staging a menudo hereda una configuración de producción diseñada para otro proxy.
Haz que el borde trabaje con Políticas de Tráfico
Las configuraciones de la aplicación son tu última línea de defensa. El borde ve la conexión TCP real, lo que significa que las reglas allí no dependen de confiar en una cabecera en absoluto. Las Políticas de Tráfico deben estar delante de tu app para tres funciones:
- Header rules. Sobrescribe o elimina las cabeceras
X-Forwarded-*proporcionadas por el cliente para que la app solo vea los valores establecidos por el borde. - IP rules. Permite o bloquea a los visitantes por su dirección de conexión real, de modo que una lista blanca no pueda ser eludida con una cabecera forjada.
- Rate limits keyed by IP. Limita a cada visitante por separado, para que un solo cliente ruidoso no agote la cuota de todos, lo cual es exactamente lo que sucede cuando tu app se basa en la dirección del agente.
Las reglas del borde y las configuraciones del framework se respaldan mutuamente. Si alguna vez se cuela una configuración incorrecta de trustProxy, el borde ya ha normalizado las cabeceras y aplicado límites sobre la dirección real.
Preguntas frecuentes
¿Es seguro usar X-Forwarded-For para la limitación de tasa? Solo las entradas añadidas por proxies de confianza. Lee la cabecera desde la derecha, omitiendo proxies de confianza, y trata el valor más a la izquierda como controlado por el visitante.
¿Por qué obtengo un bucle de redirección solo a través del túnel? Tu app ve HTTP sin cifrar porque TLS termina en el borde. Configura tu framework para leer el esquema reenviado, desde una cabecera que el proxy establece de forma fiable.
¿Debería usar trust proxy: true? No detrás de un túnel que añade cabeceras. Confía en la dirección del agente.
¿Son seguros los valores numéricos de confianza? En Fastify no lo eran, hasta que 5.12.1 los desactivó. Prefiere direcciones explícitas en todas partes.
¿Esto solo afecta al desarrollo local? No. El mismo modelo de confianza se aplica en producción detrás de cualquier proxy o balanceador.
Fuentes
- Fastify CVE-2026-3635 record y GCVE entry
- Fastify CVE-2026-16732, Red Hat y Debian tracker
- proxy-addr CVE-2026-90711 y Red Hat bug 2547485
- @fastify/proxy-addr CVE-2026-92395
- Express: behind proxies
- Fastify Request reference
- MDN: X-Forwarded-For
- ngrok HTTP endpoints y ngrok upstream headers
- Cloudflare HTTP headers y cloudflared issue 1426
- Rails ActionDispatch::RemoteIp y ActionDispatch::SSL
- Django SECURE_PROXY_SSL_HEADER warning, quoted in a DigitalOcean thread
- gunicorn deployment docs y uvicorn settings
- django-ipware
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.