csp

CSP herramientas

Content-Security-Policy en la práctica: qué gobierna cada directiva, cómo coinciden los nonces, los hashes y las expresiones de fuente, y cómo desplegar una política con Report-Only.

1 herramientas

§01 GUÍA DEL TEMA

Qué es realmente una política

Content-Security-Policy es un encabezado de respuesta formado por directivas, y cada una contiene una lista de expresiones de fuente. Antes de que el navegador solicite un subrecurso, ejecute código en línea, envíe un formulario o permita que la página quede encuadrada, consulta la directiva competente; si nada coincide, la petición no llega a ocurrir y se informa de una violación. La aplicación ocurre en el navegador, y una política solo quita capacidades, nunca las concede.

La coincidencia trabaja sobre orígenes — esquema, host, puerto — y no sobre la URL que tenías en mente: 'self' en https://app.example.com no cubre ni https://cdn.example.com ni sus subdominios. Una política vale además solo tanto como su vía de escape más débil: script-src 'self' 'unsafe-inline' entrega a cualquiera que pueda inyectar marcado una etiqueta de script funcional, que es justo el ataque que la CSP existe para detener. Sustituye 'unsafe-inline' por un nonce por respuesta o por un hash del contenido.

Las políticas se componen por intersección, nunca por unión: cuando una respuesta lleva dos encabezados Content-Security-Policy — el tuyo más uno que añadió un CDN — un recurso debe satisfacer los dos, así que un encabezado adicional solo endurece la página.

Referencia de directivas

DirectivaGobierna¿Recurre a default-src?
script-srcelementos script, eval, manejadores en línea
style-srcelementos style, enlaces a hojas de estilo, @import, atributos style
connect-srcfetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon
img-src / font-src / media-src / manifest-srcimágenes y srcset; tipografías web; audio, vídeo, track; manifiesto de la aplicación
object-srcobject y embed — siempre 'none'
frame-src / child-src / worker-srcdocumentos anidados; workerssí, los workers vía child-src
base-uri / form-actionvalores que puede fijar una etiqueta base; adónde pueden enviar los formulariosno
frame-ancestorsquién puede incrustar esta página; sustituye a X-Frame-Optionsno
sandbox / require-trusted-types-forbanderas de aislamiento en este documento; sumideros de XSS en el DOMno
report-uri / report-toadónde se envían por POST los informes de violaciónno

La columna de la derecha es la trampa: default-src 'self' deja aún abiertos el secuestro de la etiqueta base, la exfiltración por formulario y el secuestro de clic. script-src-elem y script-src-attr separan los elementos de los manejadores en línea.

Expresiones de fuente

ExpresiónCoincide conA tener en cuenta
'self' / 'none'el esquema, host y puerto exactos del documento / nadano los subdominios; 'none' queda anulado junto a cualquier otro valor
https:cualquier host en ese esquematodos los CDN de internet
https://cdn.example.comese origen, con el puerto por defecto implícitolos prefijos de ruta no se aplican a través de redirecciones
*.example.com / *cualquier subdominio, a cualquier profundidad / cualquier hostno example.com a secas; * excluye data: y blob:
'nonce-…'los elementos que llevan el atributo nonce correspondiente128 bits aleatorios o más, nuevos en cada respuesta
'sha256-…'el código en línea cuyos bytes exactos producen este hashun byte de espacio cambia el hash
'strict-dynamic'los scripts creados por un script ya confiablelas fuentes de host y de esquema pasan a ignorarse
'unsafe-inline'cualquier script o estilo en línease ignora cuando hay un nonce o un hash
'unsafe-eval' / 'unsafe-hashes'eval y new Function / hashes en onclick, style'wasm-unsafe-eval' es la versión restringida

Desplegar con Report-Only

Envía la política candidata como Content-Security-Policy-Report-Only: se evalúa igual, no bloquea nada y se puede servir junto a una política en vigor, así que puedes endurecer una segunda mientras la activa protege a los usuarios. Recoge las violaciones con report-uri /csp-reports (obsoleto, admitido en todas partes) o con report-to, que necesita un encabezado Reporting-Endpoints; la entrega de informes está exenta de connect-src. Los bloqueos de origen cruzado se reducen a un origen en blocked-uri, que nombra el host rechazado y no el archivo; añade 'report-sample' para obtener un fragmento de código y agrupa por effective-directive.

Qué herramienta para cada trabajo

Cuando un sitio está en producción, la pregunta es qué política se entrega realmente. Cabeceras HTTP solicita la URL desde api.sitekits.dev y devuelve el estado, el número de redirecciones y todos los encabezados de respuesta, nunca el cuerpo, lo que revela un proxy que haya reescrito tu política. Su protección SSRF rechaza las direcciones internas.

Si en cambio estás redactando, el Generador CSP ofrece 14 campos de directiva sobre una base endurecida default-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none', más una casilla upgrade-insecure-requests activada por defecto, así que un formulario sin tocar ya emite esa base con upgrade-insecure-requests añadido. La cadena se reconstruye a medida que escribes, en el navegador. Las directivas que quedan fuera de esos campos (report-to, sandbox, require-trusted-types-for) se añaden a mano.

Elaborar la lista de permitidos de una aplicación existente es un problema de inventario: exporta un HAR desde DevTools, léelo en el Visor HAR y pasa después las URL que te sorprendan por el Parseador de URL para reducir cada una a la forma esquema-host-puerto que exige una expresión de fuente. Sanea con el Sanitizador HAR antes de adjuntarlo a un ticket: los HAR llevan cookies y encabezados Authorization. El Diff de Texto muestra qué cambió entre las versiones report-only y en vigor; el Formateador JSON hace legible una carga útil csp-report; y el Probador REST API llama a tu destino directamente desde el navegador, sin ningún servidor de sitekits en el camino, y bajo la política de su página, que relaja connect-src a 'self' https:, no bajo la tuya. Una llamada que funciona ahí pero falla dentro de tu aplicación señala a tu propio encabezado. No te dirá la causa: un bloqueo de connect-src y un rechazo de CORS se manifiestan ambos como un único TypeError de fetch, y la herramienta imprime un solo mensaje que cubre los dos casos. También: hub de HTTP, hub de seguridad, herramientas para ingeniería de seguridad.

Roturas habituales

Añadir un nonce desactiva en silencio 'unsafe-inline'

Los gestores de etiquetas y los widgets de chat que inyectan sus propias etiquetas de script dejan de ejecutarse, porque nunca ven tu valor por respuesta; 'strict-dynamic' es la solución.

Los hashes de CSP son base64 del resumen, no hexadecimal

'sha256-…' espera base64 de los 32 bytes en bruto del resumen, así que el hexadecimal del Generador de Hash pasado por Base64 da una cadena equivocada. Cópiala del error que muestra la consola del navegador.

Un nonce escrito a fuego es 'unsafe-inline' con pasos extra

Los nonces deben regenerarse en cada respuesta, lo que los sitúa en el servidor; una constante en una plantilla — o un HTML en caché en el borde mientras el encabezado se regenera — es adivinable. El Generador UUID sirve para pruebas manuales locales, nunca para un despliegue.

Una política en etiqueta meta no puede expresar la mitad de la CSP

frame-ancestors, sandbox, report-uri y Report-Only se ignoran en meta http-equiv, y la política solo cubre el marcado que va después de ella.

FAQ
¿Por qué mi script en línea sigue bloqueado aunque script-src tenga 'unsafe-inline'?
Porque la misma directiva contiene además un nonce o un hash. Los navegadores ignoran de forma deliberada 'unsafe-inline' en cuanto aparece una fuente 'nonce-…' o 'sha256-…' en esa directiva, así que el respaldo deja de aplicarse en silencio. Estampa el nonce actual en el script en línea, añade su hash, o añade 'strict-dynamic' para que los scripts cargados por un script ya confiable hereden la confianza.
¿default-src cubre todas las directivas?
No, y este es el agujero más habitual en una política por lo demás estricta. base-uri, form-action, frame-ancestors, sandbox, report-uri y report-to no recurren a default-src, así que default-src 'self' por sí solo sigue permitiendo el secuestro de la etiqueta base, el envío de formularios a cualquier host y que cualquier sitio te encuadre. Esas hay que escribirlas de forma explícita.
¿Cómo despliego CSP sin romper producción?
Envía la política candidata como Content-Security-Policy-Report-Only, que se evalúa exactamente igual que el encabezado real pero no bloquea nada y puede servirse junto a una política en vigor. Recoge informes durante una o dos semanas y cambia después el nombre del encabezado. Espera una proporción alta de ruido inservible procedente de las extensiones del navegador, que inyectan scripts en línea en tus páginas.
¿Conviene usar un nonce o un hash para los scripts en línea?
Usa un nonce cuando el HTML se genera en cada respuesta, ya que el valor debe ser impredecible y nuevo cada vez. Usa hashes cuando el HTML es estático o está en caché en un CDN, porque un hash no necesita aleatoriedad en el servidor, pero cubre los bytes exactos del bloque en línea, así que un espacio cambiado lo invalida. Ambos se combinan con 'strict-dynamic', que propaga la confianza desde lo que la directiva ya permitía: script-src 'sha256-…' 'strict-dynamic' https: 'unsafe-inline' es la política estricta basada en hashes documentada para ese caso estático. La confianza alcanza solo a los scripts que el bloque con hash crea en tiempo de ejecución, no a las etiquetas que ya están en el marcado.
¿Por qué mi informe de violación solo nombra un dominio en blocked-uri en lugar del archivo bloqueado?
Porque las violaciones de origen cruzado se recortan a propósito al origen: comunicar la URL completa entregaría a la página la información que la CSP acaba de impedirle leer. Averiguas qué host se rechazó, no qué recurso, y tras una redirección puedes obtener solo el origen inicial. Añade 'report-sample' a la directiva para que los informes lleven un fragmento corto del código infractor, y agrupa por effective-directive para ver qué regla se está disparando de verdad. Las violaciones del mismo origen no se recortan, así que esos informes sí llevan la ruta completa.