Cómo previsualizar sitios externos de forma segura — implementación con proxy + sanitización + iframe sandbox

HeatMapX Engineering Team5 min read
  • engineering
  • security
  • nextjs
  • iframe

Resumen de este artículo

  • Cargar un sitio externo en un iframe del propio origen conlleva el riesgo de que el JS externo pueda interactuar con la sesión propia
  • La solución es una defensa en varias capas: "proxy con protección SSRF + eliminación de JS externo + CSP restrictivo + iframe sandbox"
  • Si sandbox es demasiado restrictivo, bloquea incluso los propios scripts. Olvidar añadir allow-scripts es una trampa que hace que la función deje de funcionar

Los tests A/B de HeatMapX incluyen una función que permite "previsualizar el aspecto tras el cambio (Variante B) ya aplicado a la página de destino". Técnicamente, esto consiste en "mostrar de forma segura el sitio externo del usuario dentro de un iframe del propio dominio", un proceso sorprendentemente delicado. Compartimos aquí el diseño y las trampas reales en las que caímos.

Por qué una implementación ingenua es peligrosa

Si se obtiene el HTML de un sitio externo tal cual y se sirve desde el propio origen (por ejemplo, heatmapx.com), las etiquetas <script> u atributos onclick= contenidos en ese HTML se ejecutarían con los privilegios del propio origen. Esto podría permitir la lectura de cookies o de la sesión de inicio de sesión. Es algo que debe evitarse a toda costa.

Estructura de la defensa en varias capas

Por ello, superponemos las siguientes capas.

1. Proxy con protección SSRF

Se valida la URL de destino y se rechaza cualquier esquema que no sea http/https. Además, se bloquean localhost, las IP privadas y los endpoints de metadatos de la nube (como 169.254.169.254). Las redirecciones no se siguen automáticamente: con redirect: 'manual' se revalida cada salto uno por uno, para evitar que se redirija hacia la red interna.

2. Eliminación del JS externo (sanitización)

Del HTML obtenido se eliminan las etiquetas <script>, los controladores inline on*= y el esquema javascript:. No es necesario ejecutar el JS del sitio del cliente en la vista previa (asumimos como limitación conocida que las SPA no se pueden reproducir por completo).

3. CSP restrictivo y base href

Justo después de <head> se inserta un <base href> para resolver las rutas relativas de imágenes y CSS, y a la respuesta se le aplica un CSP restrictivo que impide la ejecución de JS externo. Las imágenes y el CSS sí se permiten, ya que son necesarios para la visualización.

4. iframe sandbox

Por último, se aísla el lado de la visualización añadiendo sandbox al iframe.

La trampa en la que caímos: un sandbox demasiado restrictivo

La vista previa inyecta en el HTML obtenido un pequeño script propio que "aplica los cambios (changeSet)" y lo ejecuta dentro del iframe para modificar el aspecto visual. Sin embargo, el sandbox del iframe de vista previa solo tenía allow-same-origin, y le faltaba allow-scripts.

Como resultado, el script propio tampoco se ejecutaba, y se producía un fallo por el cual se mostraba la página original sin que se aplicaran los cambios. El iframe del lado del editor funcionaba correctamente con allow-scripts allow-same-origin, así que la causa era una discrepancia en la configuración.

La solución fue simple: bastó con igualar también el lado de la vista previa a allow-scripts allow-same-origin. Además, añadimos una prueba de regresión que verifica el atributo sandbox del iframe.

sandbox="allow-same-origin"  →  sandbox="allow-scripts allow-same-origin"

Lecciones aprendidas

  • La incrustación de contenido externo debe plantearse como una "defensa en varias capas". No hay que confiar en una única medida.
  • sandbox no es "seguro con solo añadirlo": lo esencial es conceder con precisión únicamente los permisos necesarios. Si es demasiado restrictivo, bloquea incluso la propia funcionalidad.
  • Cuando existen "dos rutas que realizan el mismo proceso" (el editor y la vista previa), hay que verificar siempre que los atributos de seguridad estén alineados. Corregir solo uno puede romper el otro.

Conclusión

La vista previa segura de sitios externos se logra mediante una defensa en varias capas: protección SSRF, eliminación de JS externo, CSP restrictivo e iframe sandbox. Y los permisos de sandbox son problemáticos tanto si faltan como si sobran. El olvido de allow-scripts de esta ocasión fue un ejemplo típico de ello.

Heatmaps desde Claude Code — gratis para empezar.

Pega una etiqueta de tracking y recibe análisis y sugerencias CRO desde la CLI.