Validador de CSP

La mayoría de políticas permite más de lo que su autor cree.

Pega una Content-Security-Policy y mira qué dejará pasar realmente un navegador. No nos llega nada: la política se interpreta en tu navegador, así que puedes revisar una cabecera que aún no has desplegado.

Funciona en tu navegador. No nos llega nada.

Qué comprueba esto, y cómo es una buena respuesta

Qué directiva gobierna de verdad tus scripts

Los navegadores usan script-src si está presente y recurren a default-src si no lo está. Ese fallback es una fuente común de sorpresas: una política con un default-src estricto y un script-src permisivo es exactamente tan permisiva como el segundo, y la línea estricta de arriba no hace nada por los scripts. Te mostramos qué directiva leímos para que el veredicto sea comprobable y no algo que tengas que aceptar por fe.

unsafe-eval

Permite convertir cadenas en código ejecutable, que es de lo que depende la mayoría de los payloads de inyección. Suele estar porque un ajuste de bundler o una librería de plantillas lo pidió, y suele poder quitarse cambiando ese ajuste. Mientras esté ahí, buena parte del resto de la política es decoración.

Comodines, y cuándo no cuentan

Un * suelto, o una fuente de esquema como https:, permite script desde cualquier host de internet. Pero un comodín de host junto a 'strict-dynamic' no es un agujero, porque esa palabra clave le dice al navegador que ignore las fuentes de host por completo y confíe solo en lo que cargue un script aprobado por nonce. Comprobamos tokens enteros en lugar de buscar en el texto, así que https://cdn.ejemplo.com/* se lee correctamente como un host concreto y no como un comodín solo porque contenga un asterisco.

unsafe-inline, y cuándo ya se está ignorando

El motivo más común de que una política de apariencia estricta no se sostenga. Permite scripts inline, así que el markup inyectado se ejecuta. El matiz que hace tropezar a la mayoría de comprobadores: si la misma directiva lleva un nonce o un hash, los navegadores ignoran 'unsafe-inline' por completo. Las políticas se escriben así a propósito para que los navegadores antiguos, que no entienden nonces, sigan cargando la página. Marcar esa forma sería un falso positivo sobre la política estricta correcta más común que existe, así que lo reportamos como correcto y decimos por qué.

base-uri, la que casi siempre falta

base-uri no la cubre default-src, así que una política puede parecer completa y aun así omitirla. Sin ella, una etiqueta <base> inyectada reapunta cada script y enlace relativo de la página a otro host, lo que convierte una pequeña inyección de HTML en ejecución completa de scripts y esquiva limpiamente un script-src en el que trabajaste mucho. Cuesta una directiva.

object-src y frame-ancestors

object-src 'none' cierra el contenido de plugin, que casi ninguna aplicación necesita y que si no puede ejecutar un documento en tu origen. frame-ancestors decide quién puede incrustar tu página; es el reemplazo moderno de X-Frame-Options y la única de las dos capaz de expresar una lista de orígenes permitidos.

Cómo corregirlo

Una política estricta es más fácil de alcanzar de lo que sugiere su fama, siempre que la despliegues primero en modo report-only y leas qué se rompe. El enfoque de nonce de abajo es el que usa este mismo sitio, así que es un patrón que corremos en producción y no uno que simplemente recomendamos.

Una política estricta para empezar
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none'

# 'strict-dynamic' lets a nonce-approved script load what it needs
# without you listing every CDN, which is what makes a strict policy
# survivable in a real app with a bundler.
Despliégala con red primero
# Same policy, different header name. Browsers report violations
# to the console and to report-to, and block NOTHING. Run it for a
# week, read what shows up, then rename the header.
Content-Security-Policy-Report-Only:
  default-src 'self'; script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'
Generar el nonce (Cloudflare Worker)
// The nonce must be new on EVERY request. A nonce baked into a
// static file is not a nonce, it is a password the attacker also has.
const nonce = crypto.randomUUID().replaceAll("-", "");

const policy = [
  "default-src 'self'",
  `script-src 'nonce-${nonce}' 'strict-dynamic'`,
  "object-src 'none'",
  "base-uri 'self'",
  "frame-ancestors 'none'",
].join("; ");

// Then stamp the same nonce onto every <script> as you stream the HTML.
return new HTMLRewriter()
  .on("script", { element: (el) => el.setAttribute("nonce", nonce) })
  .transform(new Response(html, { headers: { "content-security-policy": policy } }));
Middleware de Next.js
// middleware.ts
import { NextResponse } from "next/server";

export function middleware(request: Request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const policy = `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'self'`;

  const headers = new Headers(request.headers);
  headers.set("x-nonce", nonce);

  const response = NextResponse.next({ request: { headers } });
  response.headers.set("content-security-policy", policy);
  return response;
}
Lo que esta herramienta no puede ver

Esto lee la política que pegaste. No puede decirte si esa es la política que tu servidor envía de verdad, y las dos discrepan más a menudo de lo que esperarías: un proxy, una regla de CDN o una segunda cabecera a nivel de framework pueden sobrescribirla o duplicarla, y cuando llegan dos políticas el navegador aplica la intersección de ambas. Tampoco puede decirte si la política rompe tu app, cosa que solo mostrará un despliegue en report-only. Para ver qué envía tu sitio en vivo, lanza el comprobador de cabeceras de seguridad contra la URL.

Analizar también el códigoGratis para empezar. Conecta un repositorio y el primer análisis tarda unos dos minutos.