Qué es CSP y por qué importa en 2025
Content Security Policy (CSP) es una política que tu navegador obedece para decidir qué recursos puede cargar y ejecutar tu sitio (scripts, estilos, imágenes, iframes, conexiones, etc.). Su objetivo es reducir superficies de ataque, especialmente XSS y inyecciones que provienen de contenido no confiable. Se aplica principalmente con una cabecera HTTP (Content-Security-Policy) o, en casos puntuales, con una etiqueta <meta http-equiv="Content-Security-Policy"> en el HTML.
¿Por qué te debe importar hoy?
Los stacks modernos combinan bibliotecas de terceros, etiquetas de marketing y iframes: todo eso es potencialmente peligroso si no está bajo control.
CSP añade una última línea de defensa: aunque haya un bug que permita inyectar HTML, el navegador no ejecutará el script si la política lo prohíbe.
Permite modo “report-only” para observar violaciones antes de hacer cumplir las reglas.
En el ejemplo de LunaShop, al activar CSP en modo “report-only” aparecieron decenas de violaciones por
onclickinline y por scripts que el chat de atención al cliente inyectaba dinámicamente. Eso nos mostró dónde endurecer sin romper la tienda.
Cómo funciona una política CSP: directivas clave explicadas
Una política CSP se compone de directivas. Cada directiva controla un tipo de recurso y recibe una lista de fuentes permitidas (origins, esquemas, palabras clave especiales).
Directivas imprescindibles
default-src: valor por defecto para todo lo que no tenga una directiva específica.script-src: fuentes de scripts. Aquí vivirán nonces, hashes, y palabras clave como'unsafe-inline'(evítala) o'strict-dynamic'.style-src: fuentes de CSS (cuidado con'unsafe-inline'; mejorhashespara estilos críticos).img-src: imágenes (incluye data URIs si las usas:data:).connect-src: XHR/fetch/WebSocket/EventSource. Clave para APIs y analytics.frame-ancestors: quién puede embeber tu sitio en un iframe (anti-clickjacking).base-uri: restringe la etiqueta<base>.object-src: debe ser'none'en 2025 (plugins legacy).Otras comunes:
font-src,media-src,frame-src(ochild-srcen políticas antiguas),manifest-src,worker-src.
Palabras clave y esquemas útiles
'self','none',https:,data:,blob:.Nonces:
'nonce-r4nd0m...'generados por el servidor en cada respuesta.Hashes:
'sha256-...','sha384-...', etc. sobre el contenido exacto del script/estilo.'strict-dynamic': si un script con nonce o hash válido añade más scripts, esos heredan confianza (evita listas interminables de orígenes).
En LunaShop,
object-src 'none'eliminó advertencias de un viejo widget Flash olvidado en una landing histórica.
Strict CSP con nonces/hashes (y strict-dynamic): la estrategia recomendada
Las allowlists de hosts (script-src https://cdn1.com https://cdn2.com …) no escalan y no detienen inyecciones si un host confiable se compromete. La estrategia moderna es:
Eliminar inline inseguro: no uses
onclick=…nieval(). Migra a event listeners y APIs seguras.Aplicar nonces o hashes a cada script/estilo que controles:
Nonces: el servidor genera un valor aleatorio por respuesta (por ejemplo, en Nginx/Node) y lo inserta en la cabecera y en los
<script nonce="...">.Hashes: útiles para bloques inline estáticos (el hash debe corresponder al contenido exacto).
Añadir
'strict-dynamic'enscript-srcpara que scripts con nonce/hash puedan cargar dependencias sin abrir la política.Cerrar puertas:
object-src 'none'; base-uri 'none';y, si no usaseval, evita'unsafe-eval'.
Ejemplo de cabecera (enforcement)
En el caso ficticio, cuando el chat externo empezó a inyectar un loader adicional,
'strict-dynamic'lo permitió solo porque el script padre llevaba nonce válido. Si el proveedor intentaba cargar desde otro dominio no previsto sin pasar por el script con nonce, el navegador lo bloqueaba.
Despliegue seguro: de report-only a enforcement sin romper nada
El gran miedo con CSP es “¿y si bloqueo producción?”. Por eso existe Content-Security-Policy-Report-Only:
Activa Report-Only: misma política pero sin bloqueo; el navegador reporta violaciones a tu endpoint.
Centraliza los reportes: usa Reporting API (
report-to/Reporting-Endpoints) o, por compatibilidad,report-urisi aún lo necesitas.Agrupa y filtra: detecta patrones (misma directiva, mismo origen bloqueado) para priorizar arreglos.
Arregla en código: migra inline handlers, añade nonces/hashes, declara orígenes necesarios en
connect-src, etc.Pasa a enforcement gradualmente: primero a una parte del tráfico (canary), luego al 100%.
Ejemplo (report-only)
En LunaShop, 2 semanas de report-only fueron suficientes: el 80% de las violaciones venía de
onclickhistóricos. Tras migrarlos, el paso a enforcement no rompió el checkout.
Recetas rápidas de CSP para casos comunes
Starter policy segura
Permitir Google Analytics (gtag/analytics.js) sin unsafe-inline
Google Fonts (CSS + font files)
Iframe de pasarela de pago (ejemplo genérico)
Tip del ejemplo: para etiquetas de marketing impredecibles, en LunaShop opté por cargar todo marketing a través de un único script con nonce que administro yo; ese script decide qué proveedores activar, reduciendo variabilidad.
Depuración: errores típicos en consola y cómo resolverlos
| Mensaje/violación | Directiva implicada | Causa común | Solución práctica |
|---|---|---|---|
| Refused to execute inline script | script-src | onclick, <script>…</script> inline | Migrar a listeners; usar hash si es 100% estático; mejor nonce en <script> |
| Refused to load the script from ‘https://x’ | script-src | CDN/tercero no declarado | Añadir origen a script-src o cargar a través de tu script con nonce + 'strict-dynamic' |
| Refused to connect to ‘https://api.x’ | connect-src | Fetch/XHR a API externa | Declarar el dominio en connect-src |
| Refused to load the image | img-src | Píxel/analytics o data: no permitido | Añadir dominio o data: a img-src |
| Blocked by frame-ancestors | frame-ancestors | Intentan embeberte | Define quién sí puede (o 'none') |
| base-uri violated | base-uri | Uso de <base> | Fija base-uri 'none' o al dominio que corresponda |
En mi “sandbox” de LunaShop, un error insistente de
evalvenía de una lib legacy. Se resolvió cambiando la lib por una versión que no usabanew Function()y manteniendo fuera'unsafe-eval'.
Caso práctico (ficticio): un e-commerce pasando a Strict CSP
Contexto
Stack: Next.js (SSR), Nginx, CDN.
Terceros: Analytics, chat de soporte, mapa de tienda física, pasarela de pago embebida.
Pasos aplicados
Habilitar report-only con
script-src 'nonce-…' 'strict-dynamic'.Instrumentar Reporting API hacia
/csp-reports.Corregir violaciones: eliminar
onclick, mover estilos críticos a archivos, añadir hash a un snippet estático.Canary de enforcement en el 10% del tráfico; luego 100%.
Resultados (del ejemplo)
Violaciones por minuto: −86% tras 1 semana.
Eliminación total de
'unsafe-inline'y'unsafe-eval'.Checkout y chat funcionando bajo la nueva política.
Snippet Nginx (ilustrativo)
Moraleja del caso: nonces + strict-dynamic te permiten controlar el cómo se cargan scripts, no solo desde dónde.
Checklist final y FAQs de CSP
Checklist
Empezar con report-only y endpoint de reportes.
Quitar
onclick/inline; evitareval.Añadir nonces a todos los scripts propios y hashes solo si el contenido es fijo.
Incluir
'strict-dynamic'enscript-src.Cerrar
object-src 'none'ybase-uri 'none'.Declarar orígenes mínimos en
connect-src,img-src,font-src,frame-src.Canary → enforcement total.
Monitorizar reportes y revisar en releases futuros.
FAQs
¿Puedo usar solo allowlists de dominios? Sí, pero no es lo ideal: no te protegen ante inyección si un dominio permitido sirve contenido malicioso.
¿Nonce o hash? Nonce para scripts generados dinámicamente o externos controlados; hash para bloques inline estáticos.
¿Qué rompe más a menudo? Handlers inline y librerías que usan
eval. Empieza refactorizando eso.¿Meta tag o cabecera HTTP? Cabecera HTTP es preferible; la meta puede llegar tarde en algunos escenarios y no cubre todo.
Conclusión
CSP bien aplicada no es una lista de hosts, es una estrategia: el servidor autoriza (nonce/hash), el navegador aplica (strict-dynamic) y los reportes te guían. Si avanzas con report-only, arreglas lo obvio y pasas a enforcement con canary, obtendrás una política robusta y mantenible sin sustos en producción.