Saltar al contenido
Development

La CSP que solo se rompía en producción

Por Victor Da Luz
securitycloudflareastrodev-logsite

Agregar encabezados de seguridad debía ser el issue aburrido. No existía ningún encabezado, así que ya tenía una política sugerida resuelta desde un spike anterior, y la tarea parecía ser copiar y pegar una cadena de Content-Security-Policy en un archivo de configuración. No se quedó aburrido.

La primera sorpresa fue estructural. Este sitio es Astro sobre Cloudflare Workers, y el plan pedía dos mecanismos: middleware para las páginas renderizadas en el servidor, un archivo _headers para las estáticas. Fui a escribir el middleware y antes revisé algo: cada una de las rutas de este sitio está prerenderizada. No la mayoría, todas. El middleware en Astro solo corre para páginas renderizadas bajo demanda, y no hay ninguna. Lo escribí de todos modos, para cuando aparezca una ruta dinámica real, pero lo etiqueté con honestidad: hoy no hace nada. El archivo _headers es lo único que toca una sola solicitud real.

La segunda sorpresa vino de leer de verdad la documentación de Cloudflare en vez de asumir que ya entendía la sintaxis de _headers. Quería una política estricta en todos lados, y una más permisiva solo para la carpeta que contiene un juego de Unity WebGL, ya que WebAssembly necesita permisos que una página normal no necesita. Mi instinto fue agregar una segunda regla para esa carpeta con los permisos extra añadidos. La documentación de Cloudflare me detuvo: las reglas que coinciden con la misma solicitud no se sobrescriben entre sí, se combinan, y los encabezados duplicados se unen con comas. Un navegador que lee dos CSP unidas por comas no elige una, aplica la intersección, la versión más estricta de cada regla. Mi política “más permisiva” para el juego habría quedado silenciosamente reducida de vuelta a la estricta. Existe una sintaxis real para anular esto, un signo de exclamación antes del nombre del encabezado, que borra lo anterior para poder escribir algo genuinamente distinto. Sin haberlo revisado, habría publicado una política que se veía permisiva en el archivo y se comportaba exactamente tan restrictiva como la que supuestamente estaba aflojando.

Incluso con eso arreglado, que el encabezado se viera bien en un archivo de texto no decía nada sobre si el juego iba a funcionar. Así que lo cargué. Chrome sin interfaz, apuntado directo al documento HTML propio del juego, leyendo lo que la consola realmente decía. La primera corrida falló de inmediato: el juego trae su propio script pequeño en línea, y mi política no permitía scripts en línea. Lo volví a abrir y lo permití, solo para esa ruta. La segunda corrida, más tarde, falló de nuevo, esta vez más adentro, Unity levanta un web worker desde una URL blob, y resulta que el permiso para crear el worker no es el mismo permiso que el worker necesita para cargar su propio código. Ese segundo caso vive bajo script-src, no worker-src, y nada de la primera falla lo habría indicado; solo lo encontré porque dejé que la página corriera lo suficiente para llegar a ese paso.

La última falla fue la que ninguna prueba local podría haber detectado. Todo pasaba contra el runtime real de Cloudflare corriendo en mi máquina. Fusioné, desplegué, y apunté un navegador sin interfaz al sitio real en producción para cerrar el círculo, y falló en algo nuevo: Cloudflare mismo inyecta un script de analítica en cada respuesta real, un paso que solo ocurre en su edge, nunca en la vista previa local. Mi política no sabía que ese script existía porque no había forma de saberlo, salvo llegando hasta producción.

Cuatro fallas distintas, cuatro razones distintas por las que un encabezado que “se ve bien” no lo estaba, y cada una de ellas solo salió a la luz porque insistí en observar la carga real de la página en vez de confiar en que el archivo estaba correcto. El último arreglo salió como un push de seguimiento rápido una vez que el sitio ya estaba en producción, permitió el script extra, y confirmó todo limpio.

Lecturas relacionadas

Development

El error de CSP que no me correspondía arreglar

Una lista de lanzamiento, una violación de Content Security Policy que señalaba el script exacto que acababa de editar, y un comentario en el repositorio que habría evitado todo el desvío.

Leer