Cómo funciona esta comprobación
Scanaris analiza el HTML de tu página principal y recorre todos los elementos con atributo src, href o action: scripts, imágenes, hojas de estilo, enlaces, formularios. Para cada valor comprueba si apunta a un host que no debería existir fuera de tu máquina o tu red: localhost, 127.0.0.1, 0.0.0.0, las IP privadas 10.x.x.x, 192.168.x.x y 172.16.x.x a 172.31.x.x, y cualquier dominio acabado en .local. Si encuentra alguno, abre un hallazgo de severidad baja con las primeras referencias. Solo se examina el HTML inicial; lo que el JavaScript construya después no se ve por esta vía.
Por qué importa
Una referencia a http://localhost:3000/api/... en producción tiene dos consecuencias. La primera es funcional: para tu usuario, localhost es su propio ordenador, así que esa imagen no carga, ese formulario se envía a ninguna parte y ese script falla en silencio. La segunda es de información: le estás contando a cualquiera que tu API corre en el puerto 3000, que tu backend interno se llama api.empresa.local o que tu red usa el rango 10.0.1.x. Nada de eso es un fallo explotable por sí solo, de ahí la severidad baja, pero son piezas que un atacante guarda para después, y además delatan que el despliegue se hizo sin revisar.
Por qué pasa
Casi siempre es una URL escrita a mano durante el desarrollo que nadie cambió. La IA que generó el proyecto puso http://localhost:8000 en el fetch del formulario, o en el src de una imagen de prueba, y funcionó en local, así que se quedó. Otras causas: un .env de desarrollo que acabó en el build de producción, un WordPress migrado desde un entorno local con la URL del sitio sin actualizar, o un action de formulario copiado de un ejemplo.
Cómo arreglarlo
- Saca las URL del código. La base de tu API va en una variable de entorno (
NUXT_PUBLIC_API_BASE,NEXT_PUBLIC_API_URL,VITE_API_URL) con un valor distinto por entorno. En producción, la plataforma (Vercel, Netlify, Railway) la inyecta en el build. - Usa rutas relativas siempre que el recurso viva en tu propio dominio:
/api/contactoen lugar dehttp://localhost:3000/api/contacto. Funcionan igual en local y en producción. - WordPress: actualiza las URL del sitio en Ajustes generales y reemplaza las URL antiguas en la base de datos con una herramienta que respete la serialización.
- Busca en el build antes de desplegar. Un paso de CI que falle si el resultado compilado contiene estos hosts corta el problema de raíz:
grep -rEl "localhost|127\.0\.0\.1|192\.168\.|\.local\b" dist/ .output/public/ 2>/dev/null
Si sale algún archivo, hay una referencia que arreglar antes de subir.
Comprueba que está arreglado
curl -s https://tudominio.com | grep -oE '(src|href|action)="https?://[^"]+"' | grep -iE "localhost|127\.0\.0\.1|0\.0\.0\.0|10\.[0-9]+\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|\.local\b"
No debería salir nada. Prueba también una o dos páginas interiores, sobre todo las que tengan formularios.
Preguntas frecuentes
Es una imagen de prueba que nadie ve. ¿Importa?
El navegador de cada visitante intenta cargarla desde su propio ordenador, y el HTML sigue anunciando tu host interno. Cámbiala o bórrala; es un minuto.
¿Se revisa también el JavaScript compilado?
Esta comprobación no, solo el HTML. Pero una URL interna en el bundle suele ir acompañada de otras cosas que no deberían estar ahí; secretos en JavaScript busca claves y tokens, y comentarios HTML busca restos en el propio HTML. Las tres juntas cubren lo que suele quedar de un entorno de desarrollo.
Mi enlace apunta a un dominio .local a propósito.
Los dominios .local están reservados para redes locales (mDNS) y no resuelven en internet. Si lo que quieres es enlazar una herramienta interna, usa un subdominio real con autenticación. Lo que tu app filtra sin querer, con más ejemplos, está en secretos que tu app filtra en el navegador.