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
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
| Directiva | Gobierna | ¿Recurre a default-src? |
|---|---|---|
script-src | elementos script, eval, manejadores en línea | sí |
style-src | elementos style, enlaces a hojas de estilo, @import, atributos style | sí |
connect-src | fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon | sí |
img-src / font-src / media-src / manifest-src | imágenes y srcset; tipografías web; audio, vídeo, track; manifiesto de la aplicación | sí |
object-src | object y embed — siempre 'none' | sí |
frame-src / child-src / worker-src | documentos anidados; workers | sí, los workers vía child-src |
base-uri / form-action | valores que puede fijar una etiqueta base; adónde pueden enviar los formularios | no |
frame-ancestors | quién puede incrustar esta página; sustituye a X-Frame-Options | no |
sandbox / require-trusted-types-for | banderas de aislamiento en este documento; sumideros de XSS en el DOM | no |
report-uri / report-to | adónde se envían por POST los informes de violación | no |
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ón | Coincide con | A tener en cuenta |
|---|---|---|
'self' / 'none' | el esquema, host y puerto exactos del documento / nada | no los subdominios; 'none' queda anulado junto a cualquier otro valor |
https: | cualquier host en ese esquema | todos los CDN de internet |
https://cdn.example.com | ese origen, con el puerto por defecto implícito | los prefijos de ruta no se aplican a través de redirecciones |
*.example.com / * | cualquier subdominio, a cualquier profundidad / cualquier host | no example.com a secas; * excluye data: y blob: |
'nonce-…' | los elementos que llevan el atributo nonce correspondiente | 128 bits aleatorios o más, nuevos en cada respuesta |
'sha256-…' | el código en línea cuyos bytes exactos producen este hash | un byte de espacio cambia el hash |
'strict-dynamic' | los scripts creados por un script ya confiable | las fuentes de host y de esquema pasan a ignorarse |
'unsafe-inline' | cualquier script o estilo en línea | se 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.