Si administras una página web, un servidor o una aplicación, probablemente sepas que la inyección de código malicioso (Cross-Site Scripting o XSS) es uno de los vectores de ataque más comunes y peligrosos. Validar y sanear las entradas del usuario es obligatorio, pero los humanos nos equivocamos y los desarrolladores dejan brechas.
Aquí es donde entra Content-Security-Policy (CSP). Es una cabecera HTTP que actúa como la última línea de defensa de tu frontend. Le dice al navegador del usuario exactamente de qué dominios puede descargar scripts, estilos, imágenes o fuentes, y cuáles debe bloquear por completo.
El problema es que CSP es un arma de doble filo: si lo configuras a ciegas, no solo bloquearás a los atacantes, sino que destrozarás el diseño y la funcionalidad de tu propia web. En este artículo vamos a ver cómo implementar CSP en el mundo real, con criterio, pasando por fases seguras y analizando casos de uso cotidianos.
Índice de contenidos
El mayor error con CSP: Bloquear antes de observar
El error de novato más clásico en ciberseguridad defensiva web es ir a un comprobador de cabeceras, ver que te falta CSP, buscar en Stack Overflow (que más quisieran, se lo vas a preguntar a la IA), copiar una política restrictiva y pegarla en producción.
¿El resultado? El panel de pago deja de funcionar porque bloqueaste el script de Stripe, el menú desplegable no se abre porque bloqueaste el JavaScript en línea del tema, y las fuentes corporativas han desaparecido. Oops, toca recoger cable.
CSP funciona mediante el modelo Default Deny (denegar por defecto) si se usa correctamente. Si no le dices explícitamente al navegador «confía en este origen», lo va a bloquear. Por eso, implementar CSP no es un evento de una tarde, es un proceso.
Las 3 fases para implementar CSP sin romper tu web
Para llevar CSP a producción en entornos complejos (como un WordPress con 20 plugins o un e-commerce) sin que el teléfono de soporte empiece a arder, debes seguir este flujo de trabajo.
Fase 1: Observación y Report-Only
La magia de CSP es que tiene un «modo simulacro». En lugar de enviar la cabecera Content-Security-Policy, vas a enviar Content-Security-Policy-Report-Only.
Esta variante le dice al navegador: «Evalúa esta política de seguridad. Si algún recurso incumple las reglas, permítele ejecutarse para no romper la web, pero envíame un chivatazo (un reporte) en formato JSON avisándome de la infracción».
Fase 2: Análisis y recolección de métricas
Para que el navegador sepa a dónde enviar esos avisos, la política debe incluir una directiva de recolección, tradicionalmente report-uri (o la más moderna report-to).
Deberás usar un servicio de recolección de logs (como Report URI, Sentry, o un endpoint propio) para agrupar estos reportes. Durante un par de semanas, te dedicarás a revisar esos logs. Descubrirás cosas sorprendentes: plugins antiguos que inyectan estilos en el HTML, llamadas a APIs de terceros que nadie recordaba o trackers de marketing fantasma.
Tu trabajo aquí es ajustar la política de la Fase 1, añadiendo los dominios legítimos a tu lista blanca y decidiendo qué hacer con la «basura» que encuentres.
Fase 3: Fortificación (Enforcing)
Una vez que pasen los días y tu panel de reportes deje de mostrar bloqueos de recursos legítimos, estarás listo.
El paso a producción consiste simplemente en quitar el sufijo -Report-Only de la cabecera. A partir de ese momento, el navegador ejecutará los bloqueos de verdad. Has cerrado la puerta, pero te has asegurado antes de que todos los invitados legítimos ya estaban dentro.
Directivas esenciales que debes entender sí o sí
Una política CSP se construye encadenando directivas separadas por punto y coma (;). Estas son las fundamentales:
default-src: La regla comodín. Si no especificas una regla para imágenes o fuentes, heredará lo que pongas aquí. Lo ideal es que sea'none'(bloquea todo) o'self'(solo permite recursos de tu propio dominio).script-src: Controla desde dónde se puede ejecutar JavaScript. Es la directiva más crítica para mitigar XSS.style-src: Controla las hojas de estilo (CSS).img-src: Controla de dónde se pueden cargar imágenes.connect-src: Limita a dónde puede hacer peticiones tu web mediante Fetch, XMLHttpRequest (AJAX) o WebSockets.'unsafe-inline': Permite ejecutar código (<script>...</script>) directamente escrito en el HTML. Desde el punto de vista de la seguridad, es un desastre, pero muchos CMS te obligarán a usarlo. La alternativa profesional para evitarlo es usar Nonces (tokens aleatorios de un solo uso) o Hashes criptográficos del script permitido.
Cómo implementar CSP en HestiaCP de forma profesional y escalable
En infraestructuras gestionadas con HestiaCP (que utiliza Nginx bajo el capó), el mayor error de administración es intentar inyectar cabeceras modificando los archivos nginx.conf globales. La primera actualización del panel barrerá tus cambios y dejará tus dominios desprotegidos sin que te des cuenta.
Para implementar CSP correctamente y sobrevivir a las actualizaciones, debes usar el sistema nativo de plantillas de HestiaCP.
El método estándar: Plantillas personalizadas de Nginx
Esta es la vía profesional si gestionas múltiples dominios y quieres que una buena política de seguridad venga «de serie» (o si quieres poder actualizar la política de 50 webs a la vez en el futuro).
Consiste en crear tu propia plantilla web proxy. Y además lo explico porque es lo que uso yo para esta web y estuve bastante tiempo pegándome con ello:
- Accede por SSH a tu servidor y navega al directorio de plantillas de Nginx: cd /usr/local/hestia/data/templates/web/nginx/
- Nunca edites los archivos por defecto (
default.tplydefault.stpl). En su lugar, clónalos para crear tu propio estándar de seguridad. Por ejemplo: cp default.stpl csp-seguro.stpl / cp default.tpl csp-seguro.tpl - Edita tus nuevos archivos (especialmente el
.stpl, que es el que gestiona el tráfico HTTPS actual). Busca el bloqueserver { ... }y añade tu directiva CSP:add_header Content-Security-Policy "default-src 'self'; script-src 'self';" always; - Aplica la plantilla. Ve a la interfaz web de HestiaCP, edita el dominio que quieras proteger y en el desplegable «Plantilla Web Proxy» (o Web Template), selecciona tu nueva plantilla
csp-seguro. Guarda los cambios.
Esta estrategia es impecable a nivel de arquitectura. Si el mes que viene necesitas añadir un nuevo dominio a la lista blanca de toda tu infraestructura, solo editas esta plantilla y reconstruyes los dominios afectados (por ejemplo, con el comando v-rebuild-web-domains).
La excepción quirúrgica: Archivos de extensión individual
¿Qué pasa si tienes tu servidor perfectamente configurado con tu plantilla csp-seguro, pero un único dominio (un e-commerce caprichoso) necesita cargar recursos externos que romperían las reglas globales? No necesitas crear una plantilla nueva solo para esa web.
Para esos aislamientos, se usan las configuraciones extendidas a nivel de usuario:
- Navega al directorio de configuración de ese dominio específico: cd /home/TU_USUARIO/conf/web/TUDOMINIO.com/
- Crea o edita el archivo de configuración extendida para SSL:
nano nginx.ssl.conf_ext - Añade ahí la directiva
add_headercon la política relajada exclusiva para esa web y reinicia Nginx (systemctl restart nginx).
Combinando la escalabilidad de las plantillas .stpl para el grueso de tus proyectos y los archivos _ext para las excepciones, mantendrás tu panel HestiaCP seguro y 100% ordenado. páginas de error mal gestionadas donde los atacantes suelen encontrar huecos para inyectar XSS.
El «infierno» de las capas: Cloudflare vs. Servidor vs. Aplicación
En la infraestructura web moderna, una petición HTTP atraviesa múltiples capas antes de llegar al usuario: la aplicación (ej. WordPress/PHP), el servidor web (ej. Nginx en HestiaCP) y el proxy inverso o CDN (ej. Cloudflare, AWS CloudFront o un WAF).
El problema surge cuando intentas configurar CSP y duplicas la cabecera en varias capas a la vez. Por ejemplo, usas un plugin de seguridad en WordPress que inyecta su propio CSP, y al mismo tiempo, añades una regla Transform en Cloudflare para forzar otro CSP.
¿Qué hace el navegador cuando recibe múltiples cabeceras CSP diferentes? No elige la última, ni la primera. Las aplica todas de forma acumulativa e interseca las reglas.
Si la política de tu servidor Nginx dice que solo permites scripts locales (script-src 'self'), pero luego vas a Cloudflare e intentas «abrir la mano» añadiendo una política que permite Google Analytics (script-src 'self' https://www.google-analytics.com), el navegador evaluará ambas. Como la política de Nginx es más estricta y no permite Analytics, el script será bloqueado, por mucho que en Cloudflare lo hayas permitido.
Este comportamiento provoca horas de frustración creyendo que la CDN o el servidor «no están aplicando los cambios».
La regla de oro para evitar este caos: Elige un único lugar para gobernar tu política CSP.
- En la aplicación (código/CMS): Es ideal si necesitas generar Nonces dinámicos en cada petición (la opción más segura contra el código inline), pero es la más frágil si instalas plugins de terceros que sobrescriben las cabeceras.
- En el servidor (Nginx/Apache/Hestia): Es el estándar de la industria para sitios monolíticos. Centralizas el control y evitas que la aplicación modifique la seguridad.
- En el Edge (Cloudflare / AWS CloudFront): A través de Cloudflare Workers o CloudFront Functions. Es la opción más potente y moderna si gestionas múltiples servidores o usas arquitecturas serverless. Permite modificar la cabecera «al vuelo» antes de que llegue al usuario, y es excelente para inyectar CSP en aplicaciones legacy que no puedes tocar.
Si eliges Cloudflare o tu servidor, asegúrate primero de desactivar cualquier opción de «Añadir cabeceras de seguridad» en los plugins de tu CMS para evitar colisiones.
Tres escenarios del mundo real: De la paranoia a la monetización
No existe un CSP universal. El nivel de restricción depende de qué hace tu web y de quién paga las facturas. Veamos tres ejemplos comunes.
Escenario A: La Bóveda (Extra fortificada)
Contexto: Una intranet corporativa, un panel de administración a medida o una API. No hay publicidad, no hay tracking de terceros, todo el código es propio y controlas el servidor.
El CSP:HTTP
Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; frame-ancestors 'none'; form-action 'self';YAMLPor qué funciona: Es la lista blanca más estricta posible. Bloquea absolutamente todo por defecto (default-src 'none') y luego solo permite recursos alojados en tu dominio ('self'). Evita que tu web sea embebida en un iframe (mitigando Clickjacking) y solo permite enviar formularios a tu propio servidor.
Escenario B: El Blog Monetizado (AdSense, Analytics y YouTube)
Contexto: Un sitio de contenidos (WordPress, Ghost). Necesitas scripts de Google, anuncios dinámicos, fuentes externas y quizás insertar vídeos. Aquí es donde los puristas de la seguridad se echan las manos a la cabeza.
El CSP:HTTP
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com https://pagead2.googlesyndication.com 'unsafe-inline'; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; img-src 'self' data: https://pagead2.googlesyndication.com; font-src 'self' https://fonts.gstatic.com; frame-src https://www.youtube.com;YAMLPor qué funciona (y por qué duele): Tienes que abrir la puerta a dominios muy específicos. El verdadero peaje a pagar aquí es el 'unsafe-inline' en los scripts y estilos, ya que muchas redes de anuncios inyectan el código en caliente dentro del HTML. Es un CSP más débil, pero a veces es el único camino viable en sitios altamente dependientes de terceros.
Escenario C: El «Modo Radar» (Solo Reporting)
Contexto: Tienes un e-commerce complejo (PrestaShop, Magento o WooCommerce) con docenas de pasarelas de pago y scripts de logística. Sabes que si activas CSP en modo estricto en el Black Friday, la empresa quiebra.
El CSP:HTTP
Content-Security-Policy-Report-Only: default-src 'self' https://js.stripe.com; report-uri https://tudominio.report-uri.com/r/d/csp/enforce;YAMLPor qué funciona: Al usar -Report-Only, tu tienda sigue operando con total normalidad. Si un script fraudulento (como un Magecart intentando robar tarjetas de crédito) se cuela, se ejecutará, pero recibirás una alerta inmediata en tu sistema de logs gracias a report-uri. Te da visibilidad total sin riesgo de caída de ventas.
Buenas prácticas y Checklist antes de desplegar
Antes de dar el salto final y aplicar el modo Enforce, revisa esta lista:
- Usa
Report-Onlysiempre al empezar: Nunca te saltes este paso, ni siquiera en proyectos pequeños. - Cuidado con los Bookmarklets y extensiones del usuario: A veces verás reportes de bloqueos extraños generados por extensiones del navegador del usuario (como bloqueadores de anuncios o traductores). No añadas a tu lista blanca dominios basura solo porque aparezcan en tus logs.
- Evalúa el uso de Nonces: Si puedes modificar el backend, generar un token criptográfico único por cada petición HTTP y añadirlo a tus scripts inline (
<script nonce="r4nd0m...">) es infinitamente más seguro que capitular usando'unsafe-inline'. Yo lo evalué y decidí ir sin ello. - Revisa el peso de la cabecera: Un CSP gigantesco con cientos de dominios aumenta el peso de cada petición HTTP. Mantén tus listas blancas limpias y purga lo que ya no uses.
- No te obsesiones… aplica lo que puedas sin afectar al negocio. Encontrar el equilibrio entre seguridad y negocio es lo que diferencia a los verdaderos líderes en seguridad. Si tienes que «levantar la mano» con la CSP, pon fuerza en otros controles compensatorios, recuerda que el objetivo es conseguir una defensa en profundidad adecuada.
Conclusión
Content-Security-Policy es una herramienta formidable, pero no es magia. Un CSP robusto mitiga drásticamente el impacto de un ataque XSS impidiendo que el atacante logre extraer datos hacia su servidor o ejecutar scripts externos, pero nunca debe sustituir a unas buenas prácticas de programación.
Sanea tus inputs, escapa tus outputs y usa CSP como la red de seguridad del trapecista: está ahí para salvarte la vida si fallas, pero la idea es no caerse.

