SecOps Toolkit

Generador de Content-Security-Policy y Cabeceras de Seguridad HTTP

Una política de seguridad de contenido (CSP) bien definida es una de las defensas más eficaces contra el cross-site scripting (XSS) y la inyección de datos. Esta herramienta le permite construir la cabecera paso a paso, aplicar presets para servicios habituales como Google AdSense, Analytics o Stripe, y exportar la configuración lista para Nginx, Apache o Cloudflare Workers.

Un validador analiza la política en tiempo real y señala configuraciones peligrosas, como 'unsafe-inline' o 'unsafe-eval', antes de que lleguen a producción.

¿Qué es Content-Security-Policy y por qué importa?

Content-Security-Policy (CSP) es una cabecera de respuesta HTTP con la que el servidor indica al navegador qué orígenes están autorizados a cargar scripts, estilos, imágenes, fuentes, marcos y conexiones de red en una página. Si un atacante logra inyectar código en el HTML (por ejemplo, a través de una vulnerabilidad de cross-site scripting), el navegador bloqueará la ejecución o la carga de todo lo que la política no permita. La CSP no sustituye a la validación de entradas ni al escape de salidas en el servidor, pero reduce de forma drástica el impacto de los fallos que se escapan a esos controles.

Cómo evalúa el navegador una política

Cada tipo de recurso está gobernado por una directiva específica. Cuando esa directiva no existe, el navegador recurre a default-src. De forma simplificada, la decisión para cada recurso es la siguiente:

  • permitido = origen ∈ lista(directiva específica), si la directiva está definida.
  • permitido = origen ∈ lista(default-src), si la directiva específica no está definida.
  • Si no existe ninguna política aplicable, el recurso se permite sin restricciones.

Tres directivas no heredan de default-src: base-uri, form-action y frame-ancestors. Si no se declaran, quedan sin restricción. Cuando una página recibe varias políticas (por ejemplo, una cabecera y una etiqueta meta), todas se aplican y un recurso solo se carga si cumple cada una de ellas; el resultado equivale a la intersección de las políticas.

Directivas que configura esta herramienta

  • default-src: política de respaldo para todos los tipos de recurso que no tengan directiva propia.
  • script-src: controla el JavaScript. Es la directiva más importante frente a XSS.
  • style-src: hojas de estilo y bloques de estilo inline.
  • img-src y font-src: imágenes y tipografías.
  • connect-src: destinos de fetch, XHR, WebSocket y similares.
  • frame-src y frame-ancestors: qué contenido puede incrustar su página y quién puede incrustar la suya (defensa contra clickjacking).
  • object-src, base-uri y form-action: cierran vectores clásicos de inyección con plugins, etiquetas <base> y formularios.

Tipos de fuente

  • 'none': no permite nada. Es el valor correcto para object-src.
  • 'self': solo el mismo origen (esquema, host y puerto).
  • Hosts concretos como https://js.stripe.com: la opción recomendada para servicios de terceros.
  • https: o *: demasiado amplias; permiten cargar desde casi cualquier sitio.
  • 'nonce-…' y 'sha256-…': autorizan scripts inline concretos sin recurrir a 'unsafe-inline'.
  • 'strict-dynamic': confía en los scripts cargados por un script ya autorizado con nonce o hash.

Otras cabeceras de seguridad que conviene desplegar

Strict-Transport-Security (HSTS) obliga al navegador a usar HTTPS. El valor habitual de un año se calcula como max-age = 365 × 24 × 3600 = 31536000 segundos. Para entrar en la lista de precarga de los navegadores se exige además includeSubDomains y preload, y conviene hacerlo solo cuando todo el dominio y sus subdominios funcionen por HTTPS, porque revertirlo es lento.

X-Frame-Options (DENY o SAMEORIGIN) protege a navegadores antiguos frente al clickjacking; frame-ancestors es su equivalente moderno. X-Content-Type-Options: nosniff impide que el navegador reinterprete el tipo MIME de una respuesta. Permissions-Policy desactiva funciones del navegador como cámara, micrófono o geolocalización. Referrer-Policy limita la información de origen que se envía en las peticiones salientes.

Despliegue paso a paso sin romper producción

  1. Active el modo Report-Only: la cabecera Content-Security-Policy-Report-Only notifica las infracciones sin bloquear nada.
  2. Revise la consola del navegador y los informes recibidos, y añada solo las fuentes legítimas que falten.
  3. Elimine 'unsafe-inline' y 'unsafe-eval' sustituyéndolos por hashes o nonces y refactorizando el código que dependa de ellos.
  4. Cambie a la cabecera Content-Security-Policy en modo de aplicación y mantenga la monitorización activa.

Casos de uso reales

  • Sitio de contenidos con AdSense y Analytics: los presets añaden los hosts de Google necesarios. Los fragmentos de inicialización son inline, por lo que se recomienda empezar en Report-Only y estudiar los informes.
  • Tienda con Stripe: el preset autoriza js.stripe.com en script-src y frame-src, y api.stripe.com en connect-src.
  • Panel SaaS interno: una política estricta con default-src 'self', frame-ancestors 'none' y HSTS reduce la superficie de ataque ante auditorías de seguridad.
  • Sitio estático detrás de Cloudflare: el resultado puede exportarse como Cloudflare Worker que añade las cabeceras a todas las respuestas sin tocar el origen.

Errores habituales

  • Usar https: en script-src creyendo que es seguro: permite scripts de cualquier host HTTPS.
  • Permitir CDN públicas con contenido controlable por terceros, que pueden servir para eludir la política.
  • Olvidar base-uri, object-src y form-action, que no heredan de default-src o dejan vectores abiertos.
  • Confiar en la etiqueta meta para todo: no admite frame-ancestors, report-uri ni sandbox.

Preguntas Frecuentes

¿Qué diferencia hay entre 'unsafe-inline' y usar nonces o hashes?
'unsafe-inline' autoriza cualquier script o estilo inline de la página, incluido el que un atacante consiga inyectar, por lo que anula casi toda la protección contra XSS. Un nonce es un valor aleatorio distinto en cada respuesta, que se añade a la cabecera (nonce-...) y al atributo nonce de cada etiqueta script legítima. Un hash (sha256-...) autoriza un bloque inline concreto según su contenido. En sitios estáticos los hashes son la opción práctica, porque un nonce exige generar un valor nuevo en cada respuesta. Puede añadirlos en el campo de fuentes adicionales de la directiva. Con un nonce o un hash presente, los navegadores modernos ignoran 'unsafe-inline' en esa directiva.
¿Cómo pruebo una CSP sin romper mi sitio web?
Active el modo Report-Only de la herramienta. Se genera la cabecera Content-Security-Policy-Report-Only, con la que el navegador registra en la consola cada recurso que incumpliría la política, pero sin bloquearlo. Navegue por las páginas principales, revise los avisos, añada las fuentes legítimas que falten y repita el proceso. Cuando no aparezcan infracciones inesperadas, cambie a la cabecera Content-Security-Policy normal. Tenga en cuenta que en Report-Only la directiva frame-ancestors se ignora.
¿Funciona la CSP con Google AdSense y Google Analytics?
Sí, pero requiere ajustes. Los presets añaden los hosts de Google más habituales a script-src, img-src, connect-src y frame-src, aunque los fragmentos estándar incluyen código inline que exige un nonce, un hash o, como último recurso, 'unsafe-inline'. Además, AdSense puede cargar recursos desde dominios adicionales según la subasta y el formato del anuncio. Por eso conviene empezar en modo Report-Only, revisar las infracciones durante unos días y ampliar la política solo con los orígenes que realmente aparezcan.
¿Puedo usar una etiqueta meta en lugar de una cabecera HTTP?
Puede hacerlo cuando no tiene acceso a la configuración del servidor, por ejemplo en alojamientos estáticos sencillos, pero con limitaciones. La etiqueta meta http-equiv no admite las directivas frame-ancestors, report-uri ni sandbox, no soporta el modo Report-Only y solo protege lo que se carga después de que el navegador la procese. Siempre que sea posible, es preferible enviar la política como cabecera HTTP real desde Nginx, Apache, un CDN o un Worker.
¿Debo activar HSTS con preload y qué riesgos tiene?
HSTS evita que los navegadores usen HTTP, y la precarga lo aplica incluso en la primera visita. Es muy recomendable, pero exige que todo el dominio y, con includeSubDomains, todos sus subdominios funcionen correctamente por HTTPS. Los requisitos de la lista de precarga incluyen un max-age de al menos 31536000 segundos, includeSubDomains y preload. Salir de la lista es lento, así que empiece con un max-age corto, súbalo de forma gradual y solicite la precarga únicamente cuando esté seguro de que ningún servicio necesitará HTTP.