Ir al contenido

El medio de los artesanos de la web jueves, 20 de agosto de 2026

MailStudio
Seguridad

Content Security Policy: construir una política que aguante

Una Content Security Policy bien construida bloquea la ejecución de los scripts inyectados sin romper el sitio, siempre que se despliegue por etapas. Esta guía detalla las directivas útiles, el método de observación previa al bloqueo y las trampas de los sitios…

Una Content Security Policy bien construida convierte la mayoría de los fallos de inyección de scripts en simples líneas de registro. La dificultad está en desplegarla sin romper el sitio, lo que exige un método progresivo y no una cabecera copiada de un ejemplo genérico. Esta guía detalla las directivas que cuentan, cómo ponerlas en observación antes de hacerlas bloqueantes, y las trampas propias de los sitios cargados de scripts de terceros.

Para qué sirve realmente una CSP

La Content Security Policy es una cabecera HTTP con la que un servidor declara al navegador qué fuentes de contenido son legítimas para una página. El navegador aplica después esa lista: un script cargado desde un dominio no autorizado, un manejador de eventos inline inyectado por un ataque o una llamada de red hacia un servidor hostil quedan bloqueados antes de ejecutarse.

El beneficio principal afecta a los ataques por inyección de scripts, conocidos comúnmente como XSS. Incluso cuando un atacante consigue insertar una etiqueta <script> en una página, una política estricta impide que se ejecute porque no corresponde a ninguna fuente declarada. La CSP cubre además la protección frente a la inclusión en un iframe ajeno, mediante la directiva frame-ancestors, que sustituye a la antigua cabecera X-Frame-Options.

Navegador aplica la CSP Fuente declarada (self, CDN de confianza) autorizada Script inline inyectado, dominio desconocido: bloqueo + informe Endpoint de informes report-to

Las directivas que estructuran una política

Una política se lee como una sucesión de directivas separadas por punto y coma, cada una nombrando un tipo de recurso y sus fuentes autorizadas. La directiva default-src hace de red de seguridad: se aplica a cualquier tipo no cubierto por una directiva más específica.

DirectivaControlaValor de partida razonable
default-srcRed de seguridad para los tipos no especificados'self'
script-srcOrigen de los scripts JavaScript'self' + nonce
style-srcOrigen de las hojas de estilo'self'
img-srcOrigen de las imágenes'self' data:
connect-srcDestinos de las peticiones fetch, XHR y WebSocket'self' + API de negocio
frame-ancestorsQuién puede incrustar la página en un iframe'self' o 'none'
base-uriValores autorizados para la etiqueta base'self'
form-actionDestinos de envío de los formularios'self'
object-srcPlugins heredados (Flash, applets)'none'

Tres palabras clave aparecen constantemente. El valor 'self' autoriza el mismo origen que la página. El valor 'none' prohíbe toda fuente. En cambio, 'unsafe-inline' y 'unsafe-eval' vuelven a abrir la puerta al script inline y a la evaluación dinámica, y vacían la política de buena parte de su interés en cuanto afectan a script-src.

El enfoque moderno: nonces y strict-dynamic

Autorizar una lista de dominios de confianza en script-src funciona, pero envejece mal: cada nuevo servicio de terceros alarga la lista, y basta con que un solo dominio autorizado se vea comprometido para sortear la política. El enfoque recomendado hoy se apoya en los nonces: tokens aleatorios generados en cada respuesta y asociados a los scripts legítimos.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m2026' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data:;
  connect-src 'self' https://api.ejemplo.es;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';
  upgrade-insecure-requests;

Del lado de la página, cada script de confianza lleva el atributo correspondiente, con un token regenerado en cada petición y nunca reutilizado.

<script nonce="r4nd0m2026" src="/assets/app.js"></script>

La palabra clave 'strict-dynamic' completa el dispositivo: un script ya autorizado por nonce puede cargar a su vez otros scripts sin que cada dependencia deba figurar en la lista. La política se concentra entonces en la raíz de confianza y no en una enumeración de dominios que se desvía con el tiempo.

Una lista de dominios autorizados crece con cada integración; un nonce solo confía en el código que el servidor ha emitido realmente.

Desplegar sin romper el sitio

El riesgo de una CSP no es el fallo de seguridad, es la regresión: una directiva demasiado estricta hace desaparecer un mapa, un formulario de pago o una etiqueta de analítica. La solución cabe en una cabecera distinta, Content-Security-Policy-Report-Only, que aplica exactamente las mismas reglas sin bloquear nada. El navegador se limita a señalar lo que habría rechazado.

Método en tres tiempos. Publicar primero la política en Report-Only y recopilar las violaciones durante varios días de tráfico real. Corregir después las fuentes legítimas que aparezcan (widgets, tipografías, API). Pasar por último a la cabecera bloqueante una vez que el flujo de informes se haya quedado en silencio.

La recogida de informes pasa por la directiva report-to, apoyada en la Reporting API, que envía un documento JSON a un punto de destino en cada violación. Para una compatibilidad más amplia con navegadores antiguos, la antigua directiva report-uri suele mantenerse en paralelo durante la transición.

Las trampas de los sitios cargados de terceros

Un sitio corporativo servido por un servidor bajo control se presta bien a los nonces. Un sitio construido sobre un CMS extensible plantea otro problema: numerosos plugins inyectan sus propias etiquetas <script> inline, sin nonce, en el momento del renderizado. En WordPress, por ejemplo, la tentación de restablecer 'unsafe-inline' para silenciar los errores equivale a desactivar la protección en el punto más sensible.

Tres reflejos limitan los daños. Primero, inventariar los scripts de terceros realmente utilizados y retirar los que ya no sirven. Segundo, preferir las extensiones que permiten adjuntar un nonce, o trasladar los scripts inline a ficheros servidos desde el mismo origen. Tercero, tratar connect-src con tanto cuidado como script-src: es la directiva que impide la exfiltración de datos hacia un servidor ajeno, a menudo el objetivo final de una inyección lograda.

Probar y mantener la política a lo largo del tiempo

Una CSP nunca está terminada: cada nueva funcionalidad, cada integración de marketing y cada actualización de extensión puede introducir una fuente legítima que la política desconoce. Sin disciplina de mantenimiento acechan dos derivas. La primera hace que la lista de fuentes autorizadas se hinche a golpe de peticiones urgentes, hasta autorizar tanto que la protección pierde sentido. La segunda hace que la política bloquee en silencio una funcionalidad que nadie prueba, hasta que un usuario informa del fallo.

La consola del navegador sigue siendo la primera herramienta de diagnóstico: cada violación aparece con la directiva culpable y el recurso rechazado, lo que basta para detectar una regresión durante el desarrollo. Para una evaluación más sistemática, hay analizadores en línea que puntúan una política y señalan las palabras clave que la debilitan, empezando por 'unsafe-inline' y los comodines demasiado amplios. Integrar esa comprobación en la cadena de integración continua evita que una política se degrade sin que nadie lo note.

El mantenimiento gana si se enmarca en unas pocas reglas simples: documentar el motivo de cada fuente autorizada, revisar la política cada vez que se añade un servicio de terceros y conservar activo el endpoint de informes incluso tras pasar al modo bloqueante. Las violaciones que siguen llegando en producción señalan o bien un ataque en curso, o bien una funcionalidad legítima olvidada durante el endurecimiento. En ambos casos, la información merece verse.

Lo esencial

  • La CSP neutraliza la ejecución de los scripts inyectados, a condición de evitar 'unsafe-inline' en script-src.
  • Los nonces combinados con 'strict-dynamic' sustituyen con ventaja a las listas de dominios.
  • El despliegue se hace primero en Report-Only y después en modo bloqueante, una vez tratados los falsos positivos.
  • frame-ancestors, base-uri, form-action y object-src 'none' completan la protección más allá de los scripts.

La primera vez que desplegué una CSP directamente en modo bloqueante, rompí el proceso de pago de un cliente en plena jornada. Desde entonces no arranco nunca de otra forma que en Report-Only, y la dejo correr una semana entera antes de apretar. En los sitios WordPress cargados de extensiones, considero que una CSP a medio endurecer pero realmente activa vale más que una política perfecta sobre el papel que nadie se atreve a encender. — Simon Janvier

Para profundizar

Referencia de las directivas y de su compatibilidad: Content-Security-Policy en MDN.

También en Mail Studio

Compartir LinkedIn Bluesky Hacker News E-mail

Leer también