La Content-Security-Policy (CSP) es una cabecera de respuesta que indica al navegador qué scripts, estilos, imágenes, marcos y conexiones puede usar una página. Si alguien consigue colar un script en tu HTML, una buena política impide que el navegador lo ejecute. Las políticas copiadas de un foro suelen fallar de dos maneras: rompen la analítica, las fuentes web y el widget de pago, o acaban llenas de 'unsafe-inline' y comodines hasta permitir casi todo.
Esta guía reúne ejemplos que funcionan para tres tipos de sitio, explica qué hace estricta a una política (nonces, hashes y 'strict-dynamic'), repasa las directivas a las que no llega default-src, muestra cómo recoger informes de infracciones y propone un despliegue que empieza en modo Report-Only para que nada se rompa sin aviso. Todos los ejemplos usan example.com; sustituye los valores de ejemplo por los tuyos.
Alcance de esta guía
El comprobador gratuito lee las cabeceras de una sola respuesta pública: la página de inicio del origen que escribes, tras hasta cinco redirecciones. Solo lee la cabecera CSP aplicada, no una política Report-Only, no valora si la política encaja con tu sitio y no es un pentest.
1. Qué hace una Content-Security-Policy y qué no hace
Una política es una lista de directivas separadas por punto y coma. Cada directiva nombra un tipo de recurso y los orígenes permitidos para él: script-src para JavaScript, style-src para CSS, img-src, font-src, connect-src para conexiones fetch, XHR y WebSocket, y frame-src para los marcos que inserta la página. default-src actúa como valor de reserva para estas directivas de carga cuando falta una concreta (W3C CSP Level 3).
Hay tres directivas importantes que no heredan nada de default-src: frame-ancestors (qué sitios pueden enmarcar tu página), base-uri (qué puede fijar un elemento <base>) y form-action (adónde pueden enviarse los formularios). Por eso una política que solo diga default-src 'self' sigue dejando que cualquier sitio meta tu página en un iframe y que una etiqueta <base> inyectada desvíe las URL relativas.
Los valores de origen son de tres tipos:
- Palabras clave entre comillas simples:
'self'(el propio origen de la página),'none'(nada),'unsafe-inline','unsafe-eval'y'strict-dynamic'. - Hosts y esquemas:
https://cdn.example.com,https://*.example.com,https:odata:. - Nonces y hashes:
'nonce-…'y'sha256-…', que autorizan elementos script o style concretos en lugar de hosts enteros.
Envía la política como cabecera HTTP. Una etiqueta <meta http-equiv="Content-Security-Policy"> sirve para la mayoría de directivas, pero la especificación excluye ahí frame-ancestors, report-uri y sandbox, y una política Report-Only no puede entregarse por meta de ninguna forma (guía CSP de MDN). Conviene tener expectativas realistas: la CSP limita lo que puede hacer el código inyectado, pero no sustituye al escapado de la salida ni a la validación de la entrada, que son lo que evita la inyección.
2. Tres ejemplos de Content-Security-Policy para empezar
Ejemplo A: un sitio sin scripts de terceros. Una política de lista blanca funciona cuando todos los scripts y hojas de estilo son archivos de tu propio origen:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requestsBloquea cualquier <script> en línea, cualquier atributo style y todo lo que venga de otros hosts, así que encaja en sitios hechos a mano o generados de forma estática y sin contenido incrustado.
Ejemplo B: un sitio renderizado en el servidor con nonce. Es la política estricta que web.dev recomienda para páginas que el servidor genera en cada petición, con protección de marcos añadida:
Content-Security-Policy: script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'El nonce cambia en cada respuesta (sección 3). Fíjate en lo que deja fuera: al no haber default-src, estilos, imágenes, fuentes y conexiones quedan sin restricción. Es una decisión consciente: se centra en la ejecución de scripts, donde el cross-site scripting hace más daño, y evita listas de hosts que se rompen cada vez que un proveedor cambia de dominio.
Ejemplo C: páginas estáticas o en caché con hashes. Si todo el mundo recibe el mismo HTML, autoriza los scripts en línea por su hash:
Content-Security-Policy: script-src 'sha256-BASE64_HASH_OF_INLINE_LOADER' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'| Tipo de política | Encaja con | Trabajo continuo | Cuidado con |
|---|---|---|---|
| A: lista de hosts | Sitios estáticos sin código de terceros | Actualizar la lista al añadir un proveedor | Cualquier script de un host permitido puede cargarse, incluidas versiones antiguas de librerías en un CDN compartido |
| B: nonce + strict-dynamic | Páginas que tu aplicación genera en cada petición | Añadir el nonce a cada etiqueta script en las plantillas | Cachés de página que sirven el mismo nonce a todos los visitantes |
| C: hash + strict-dynamic | HTML estático, prerenderizado o en caché del CDN | Recalcular los hashes cuando cambia el código en línea | Minificadores y plantillas que alteran los espacios y, con ellos, el hash |
Elijas la que elijas, añade las directivas de informes de la sección 6 y envíala primero como Content-Security-Policy-Report-Only.
3. Nonces y hashes en lugar de 'unsafe-inline'
'unsafe-inline' permite cualquier script en línea, también el que inyecte un atacante, y con eso se pierde casi todo lo que la CSP aporta frente al cross-site scripting. Los nonces y los hashes solo autorizan el código en línea que de verdad quieres publicar. Los navegadores ignoran 'unsafe-inline' en una directiva que también contiene un nonce o un hash (MDN), y por eso puede quedarse en una política estricta como reserva para navegadores muy antiguos.
Nonces
Un nonce es un valor aleatorio que el servidor genera en cada respuesta, incluye en la cabecera y copia en el atributo nonce de cada script que quiere ejecutar. Tiene que ser impredecible y nuevo en cada respuesta; web.dev recomienda al menos 128 bits de un generador criptográficamente seguro. En una aplicación Node.js:
import crypto from 'node:crypto';
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.nonce = nonce;
res.setHeader('Content-Security-Policy',
`script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'`);
next();
});La plantilla escribe después <script nonce="…" src="/app.js"></script> con el mismo valor. Pon el nonce allí donde tus plantillas generan tus propias etiquetas script, nunca reescribiendo todos los <script> del HTML terminado: una sustitución general también daría por bueno un script inyectado. Hay dos trampas frecuentes: un CDN o una caché de página que guarda el HTML repite el mismo nonce a todos los visitantes, y un valor fijado en un archivo estático durante la compilación no es un nonce.
Hashes
Un hash autoriza un único script en línea exacto: el resumen SHA-256, SHA-384 o SHA-512, codificado en base64, del texto que hay entre las etiquetas script. Cuenta cada carácter, espacios y saltos de línea incluidos, así que un cambio en el minificador o en la plantilla genera un hash distinto. Por eso los hashes encajan mejor en páginas estáticas o en caché, donde el contenido es igual para todos. Chrome muestra en la consola el hash que esperaba cuando bloquea un script en línea; también puedes calcularlo tú:
printf '%s' 'console.log("hello")' | openssl dgst -sha256 -binary | openssl base64Lo que no permite ninguno de los dos
Los manejadores de eventos en línea como onclick="…" y las URL javascript: quedan bloqueados con una política basada en nonces o hashes. Lleva ese código a archivos de script y engánchalo con addEventListener. 'unsafe-hashes' puede autorizar un manejador concreto por su hash, pero úsalo solo como parche temporal. El código que ejecuta cadenas como código, por ejemplo eval(), new Function() o setTimeout con una cadena, necesita 'unsafe-eval'; sustitúyelo cuando puedas y recurre a 'wasm-unsafe-eval', más limitado, si solo hace falta compilar WebAssembly (MDN script-src).
4. strict-dynamic: que los scripts de confianza carguen lo que necesitan
Casi todos los sitios ejecutan scripts que cargan otros scripts: un gestor de etiquetas añade la analítica, un banner de consentimiento añade a sus proveedores, un chat trae su propio paquete. Enumerar todos los hosts que podrían usar es frágil. 'strict-dynamic' va por otro camino: un script autorizado por nonce o hash puede añadir más scripts, y esos también se consideran de confianza.
En los navegadores que lo admiten, 'strict-dynamic' hace además que se ignoren las listas de hosts, 'self' y 'unsafe-inline' dentro de script-src (MDN). Eso tiene tres consecuencias:
- Cada etiqueta
<script>de tu HTML necesita el nonce o un hash que coincida, también las de tu propio origen, porque'self'ya no las autoriza. - Los scripts que un script de confianza crea con
document.createElement('script')se permiten. Los que se escriben en la página condocument.write(scripts insertados por el analizador) no, así que los fragmentos de inserción antiguos que dependen de ello dejan de funcionar. - La confianza se transmite. Todo lo que cargue tu gestor de etiquetas se ejecutará, de modo que quien pueda publicar en él puede, en la práctica, ejecutar código en tu web. Trata ese acceso como un acceso de despliegue.
Para navegadores antiguos, añade valores de reserva que los modernos ignoran:
script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none'Un navegador compatible con CSP Level 3 aplica solo el nonce y 'strict-dynamic'. Uno que entiende los nonces pero no 'strict-dynamic' usa el nonce y https:, y uno muy antiguo se queda con 'unsafe-inline' https:. Google documenta un fragmento de Tag Manager compatible con nonces que transmite el nonce a los scripts que añade, y advierte de que las variables de JavaScript personalizadas de Tag Manager siguen necesitando 'unsafe-eval' (guía CSP de Tag Manager).
5. frame-ancestors, object-src 'none' y base-uri: directivas que debes escribir tú
frame-ancestorsdecide qué sitios pueden insertar tu página en un marco y es la defensa contra el clickjacking. Usa'none'si nadie necesita enmarcarla,'self'si solo lo hacen tus propias páginas, o enumera orígenes exactos comohttps://app.example.com. Solo funciona en la cabecera HTTP.X-Frame-Options(DENYoSAMEORIGIN) sigue siendo una reserva para navegadores antiguos; haz que ambas permitan lo mismo (MDN frame-ancestors).object-src 'none'bloquea<object>y<embed>. Los plugins ya no existen en los navegadores modernos, pero estos elementos todavía pueden cargar contenido, y una política estricta sindefault-srclos deja libres si no lo indicas.base-uri 'none', o'self'si tus páginas usan un elemento<base>, impide que un<base href>inyectado redirija todas las URL relativas de scripts a otro host.form-action 'self'limita adónde pueden enviarse los formularios. Añade el origen de tu proveedor de pago o de inicio de sesión si algún formulario envía datos allí, y revisa esos flujos después del cambio.upgrade-insecure-requestshace que el navegador cargue por HTTPS los subrecursoshttp://. Ayuda con el contenido mixto, pero no sustituye a HSTS.
Escribe estas directivas de forma explícita en todas tus políticas, también en los ejemplos B y C. Apenas requieren mantenimiento y cierran huecos que las reglas de scripts dejan abiertos.
6. Informes de infracciones con report-to y report-uri
Los informes de infracciones cuentan qué bloqueó una política, o qué bloquearía en modo Report-Only, en los navegadores de visitantes reales. CSP Level 3 define report-to, que nombra un endpoint declarado en la cabecera Reporting-Endpoints, y marca como obsoleta la antigua report-uri, que recibe una URL directamente. MDN indica que report-to está disponible en los navegadores actuales desde 2026, mientras que las versiones antiguas solo entienden report-uri. Los navegadores que admiten report-to ignoran report-uri, así que de momento envía ambas (MDN report-to):
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic' 'report-sample'; object-src 'none'; base-uri 'none'; report-uri https://example.com/csp-reports; report-to cspLos dos mecanismos envían formatos distintos. report-uri manda un objeto JSON con la clave csp-report y el tipo de contenido application/csp-report. report-to manda application/reports+json: una lista de informes cuyo type es csp-violation y cuyo cuerpo incluye campos como documentURL, blockedURL, effectiveDirective y disposition (enforce o report). Con 'report-sample', el informe de un código en línea bloqueado incluye sus primeros 40 caracteres. Acepta ambos formatos en el mismo endpoint.
- Cuenta con ruido. Las extensiones del navegador inyectan scripts y provocan infracciones con archivos de origen como
chrome-extension://omoz-extension://. Agrupa los informes por directiva y host bloqueado, y busca patrones en lugar de entradas sueltas. - Trata los informes como datos personales. Las URL de documento y los referrers pueden llevar parámetros con direcciones de correo o tokens. Elimínalos o recórtalos, guarda los informes poco tiempo y no los compartas sin motivo.
- Protege el endpoint. Cualquiera puede enviar informes falsos: aplica límites de tamaño y de frecuencia, y no muestres nunca su contenido sin escapar en un panel de administración.
7. Lo que suele romperse: analítica, fuentes, scripts en línea e incrustaciones
Casi todas las infracciones de los primeros días vienen de unas pocas fuentes. El campo effectiveDirective del informe te dice qué regla revisar:
| Síntoma | Directiva en el informe | Solución habitual |
|---|---|---|
| La analítica deja de registrar visitas | script-src-elem, connect-src, img-src | Cargar la etiqueta con nonce, o permitir su host de script, y permitir los hosts de recogida que documenta el proveedor (Google los publica por producto) |
| Las fuentes web pasan a las del sistema | font-src, style-src-elem | Permitir el host de la hoja de estilos en style-src y el de los archivos de fuente en font-src (con Google Fonts, fonts.googleapis.com y fonts.gstatic.com), o alojar las fuentes tú mismo |
| Un fragmento del tema o del CMS deja de funcionar | script-src-elem con blockedURL «inline» | Añadir el nonce en la plantilla, mover el código a un archivo o autorizar su hash |
Los botones con onclick no hacen nada | script-src-attr | Reescribir el manejador con addEventListener en un archivo de script |
| Las variables personalizadas del gestor de etiquetas no devuelven nada | script-src (eval) | Sustituirlas por variables integradas o aceptar 'unsafe-eval' como excepción documentada |
| Un vídeo, mapa o marco de pago incrustado se queda en blanco | frame-src | Añadir el origen exacto del proveedor |
| Se ignoran los estilos en línea y el diseño se descoloca | style-src-elem, style-src-attr | Mover los estilos a hojas de estilo, poner el nonce en los elementos style o mantener 'unsafe-inline' solo en style-src, como excepción documentada |
| El chat o las actualizaciones en vivo no conectan | connect-src | Añadir los hosts https:// y wss:// del proveedor |
| Tu aplicación o un socio ya no puede incrustar la página | frame-ancestors | Enumerar los orígenes exactos que la incrustan |
Los estilos en línea son un riesgo menor que los scripts en línea, pero no inofensivo: el CSS inyectado puede cambiar lo que ve el visitante y, con selectores de atributo, filtrar algunos datos de la página. Muchos frameworks y librerías CSS-in-JS insertan elementos style en tiempo de ejecución, así que comprueba si el tuyo admite nonces antes de quitar 'unsafe-inline' de style-src. Alojar tú mismo fuentes y librerías elimina varias de estas filas de una vez y mantiene la política corta.
8. Cómo desplegar una CSP por etapas
- Inventario. Anota los scripts, estilos, fuentes, marcos y destinos de conexión de tus páginas clave: inicio, acceso, cuenta, pago y páginas con widgets incrustados. El panel Network de DevTools y la configuración de tu gestor de etiquetas son las fuentes más rápidas.
- Report-Only. Envía el borrador como
Content-Security-Policy-Report-Onlycon informes activados. No se bloquea nada; las infracciones aparecen en la consola y en tu endpoint. Déjalo el tiempo suficiente para que aparezcan las páginas menos visitadas y las campañas, normalmente una semana o más. - Corrige el origen, no la política. Añade nonces, saca los manejadores en línea a archivos, aloja las fuentes en tu servidor. Añade un host o
'unsafe-eval'solo como excepción documentada, con responsable y motivo. - Aplica. Cambia el nombre de la cabecera a
Content-Security-Policy. Mantén a su lado una política Report-Only más estricta para ensayar el siguiente ajuste: cuando llegan ambas cabeceras, el navegador aplica una y solo informa sobre la otra. - Sigue vigilando. Una etiqueta de marketing nueva, la actualización de un plugin o de un framework pueden traer código en línea nuevo. Mantén el endpoint de informes activo y vuelve a revisar la cabecera después de cada cambio de servidor, CDN o framework.
Dos detalles de infraestructura causan muchas sorpresas. Si un CDN o un framework añade su propia cabecera CSP, el navegador aplica todas las políticas que recibe y un recurso tiene que superarlas todas, de modo que una cabecera extra solo puede endurecer, nunca relajar (MDN). Y si el HTML se guarda en caché en el borde, una política con nonce necesita que el nonce se genere donde se monta la página, o pasarse a hashes. Para ver lo que una página envía de verdad:
curl -sS -D - -o /dev/null https://example.com/ | grep -i content-security-policyLa guía para comprobar las cabeceras de seguridad cubre las cabeceras que acompañan a la CSP, y la lista de seguridad para el lanzamiento de una web las integra en una revisión completa antes de publicar.
9. Revisa la cabecera publicada con el comprobador gratuito
Cuando la política ya se aplica, el comprobador gratuito de cabeceras de seguridad muestra lo que recibe un navegador desde tu página de inicio. Ejecuta el snapshot gratuito de preparación para el lanzamiento de Sitelemetry, ocho comprobaciones pasivas sobre un dominio público sin registro, y abre la comprobación de cabeceras por encima de las otras siete.
- Solicita la página de inicio del origen que escribes, sigue hasta cinco redirecciones y lee la respuesta final. Otras páginas, como el acceso o el pago, suelen llevar políticas distintas; revísalas con DevTools o curl.
- Solo lee la cabecera
Content-Security-Policyaplicada. Durante la etapa Report-Only seguirá indicando que falta la CSP, con riesgo medio, porque una política de solo informe no bloquea nada. Es el resultado esperado hasta que la apliques. - Una directiva
frame-ancestorsen la política aplicada cuenta como protección de marcos, igual queX-Frame-Options. - En la nota de endurecimiento, que va aparte (de A a F), la CSP suma menos cuando
default-srco una directivascript-srcostyle-src(incluidas sus variantes-elemy-attr) contiene'unsafe-inline', o cuandodefault-srco una directiva de scripts contiene'unsafe-eval'. La nota lee la política tal como está escrita, así que un'unsafe-inline'de reserva junto a un nonce también resta, aunque los navegadores actuales lo ignoren. - No evalúa nonces, hashes,
'strict-dynamic',object-src,base-urini los informes. Para eso usa la consola del navegador y tus informes de infracciones.
El resultado muestra una puntuación y el resultado de cada una de las ocho comprobaciones, de modo que la señal de la CSP aparece junto a HSTS, TLS y el resto de comprobaciones públicas.
Preguntas frecuentes
¿Qué ejemplo de Content-Security-Policy es bueno para empezar?
Para un sitio renderizado en el servidor: script-src 'nonce-{aleatorio}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self', con un nonce nuevo en cada respuesta. Para páginas estáticas o en caché, usa hashes de tus scripts en línea en lugar del nonce. Empieza con cualquiera de las dos como Content-Security-Policy-Report-Only.
¿Uso un nonce o un hash en mi CSP?
Usa un nonce cuando tu aplicación genera cada página, porque puede insertar un valor nuevo cada vez. Usa hashes cuando el HTML es estático o está en caché, porque un nonce en caché se repetiría para todos los visitantes. Ambos se combinan con 'strict-dynamic'.
¿Es un problema tener 'unsafe-inline' en style-src?
Es un riesgo menor que 'unsafe-inline' en script-src, pero el CSS inyectado puede cambiar lo que ven los visitantes y filtrar algunos datos de la página. Mantenlo solo como excepción documentada mientras pasas los estilos a hojas de estilo o añades nonces a los elementos style.
¿Content-Security-Policy-Report-Only protege mi web?
No. No bloquea nada; solo informa de lo que bloquearía una política aplicada. Úsala para ensayar y luego cambia a la cabecera Content-Security-Policy. Puedes enviar las dos a la vez, algo útil para probar la siguiente versión, más estricta.
¿Puedo definir la CSP en una etiqueta meta?
Sí, para la mayoría de directivas. Pero frame-ancestors, report-uri y sandbox se ignoran en una etiqueta meta, y una política Report-Only no puede entregarse así. Usa la cabecera HTTP siempre que tu servidor o tu hosting te lo permitan.
¿Qué diferencia hay entre report-uri y report-to?
report-uri recibe una URL directamente; CSP Level 3 la declara obsoleta, pero los navegadores antiguos todavía dependen de ella. report-to nombra un endpoint de la cabecera Reporting-Endpoints y usa el formato de la Reporting API. Los navegadores que admiten report-to ignoran report-uri, así que enviar ambas no causa problemas.
Fuentes y lecturas
- W3C: Content Security Policy Level 3www.w3.org
- MDN: guía de Content Security Policy (CSP)developer.mozilla.org
- MDN: cabecera Content-Security-Policydeveloper.mozilla.org
- MDN: directiva CSP script-srcdeveloper.mozilla.org
- MDN: directiva CSP frame-ancestorsdeveloper.mozilla.org
- MDN: directiva CSP report-todeveloper.mozilla.org
- MDN: cabecera Reporting-Endpointsdeveloper.mozilla.org
- W3C: Reporting APIwww.w3.org
- web.dev: Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP)web.dev
- Google Tag Platform: Use Tag Manager with a Content Security Policydevelopers.google.com
- OWASP Content Security Policy Cheat Sheetcheatsheetseries.owasp.org
Preparado por el equipo editorial de Sitelemetry. Consulta las fuentes enlazadas para profundizar.



