Implementación de Content Security Policy en cabeceras HTTP de WordPress

Content Security Policy (CSP) es una capa crítica de defensa que reduce ataques como Cross-Site Scripting (XSS) y el robo de datos por inyección. En WordPress, donde los temas y plugins añaden recursos externos e inline scripts, diseñar e implementar CSP en las cabeceras HTTP requiere planificación técnica para evitar roturas funcionales. Esta guía explica conceptos, directivas prácticas, ejemplos para Apache/nginx y código para integrar CSP correctamente en WordPress, además de pruebas y despliegue controlado.

Qué es CSP y por qué importa en WordPress

CSP es una cabecera HTTP que instruye al navegador sobre qué orígenes pueden cargar recursos como scripts, estilos, imágenes o conexiones. Implementar CSP protege contra XSS y mitiga el impacto de terceros comprometidos. Además, una CSP bien diseñada puede mejorar la confianza del usuario y reducir riesgo, sin afectar negativamente al SEO si se prueba y despliega correctamente.

Principios básicos y directivas esenciales

Las directivas controlan tipos de recursos. Las más relevantes para WordPress son:

  • default-src: fallback para otras directivas.
  • script-src: controla scripts; aquí se aplican nonces y hashes.
  • style-src: controla estilos; muchas veces requiere ‘unsafe-inline’ o hashes para estilos inline.
  • img-src, connect-src, font-src: para imágenes, XHR/fetch y fuentes.
  • frame-ancestors: evita que tu sitio sea embebido en iframes maliciosos.
  • report-uri / report-to: para recibir reportes de violaciones en modo report-only.

Evita usar permanentemente ‘unsafe-inline’ en script-src; en su lugar utiliza nonces o hashes.

Implementación en el servidor

Siempre que sea posible, añade CSP a nivel de servidor. Esto asegura que la política se aplica antes de cualquier modificación por plugins y es más eficiente.

Apache (.htaccess o configuración vhost)

Ejemplo mínimo para producción (ajustar orígenes reales):

Header set Content-Security-Policy 'default-src 'self'; script-src 'self' https://www.google-analytics.com; style-src 'self' https://fonts.googleapis.com; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; upgrade-insecure-requests'

NGINX

Ejemplo de bloque server:

add_header Content-Security-Policy 'default-src 'self'; script-src 'self' https://www.google-analytics.com; style-src 'self' https://fonts.googleapis.com; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; upgrade-insecure-requests';

En nginx considera la directiva always si necesitas que la cabecera se envíe incluso en respuestas 4xx/5xx.

Integración en WordPress

Si no tienes control del servidor o necesitas lógica dinámica (nonces, políticas distintas por entorno), añade la cabecera desde WordPress usando el hook send_headers.

Generar y usar nonces en WordPress

Flujo recomendado:

  • Generar un nonce por petición en init o send_headers.
  • Añadir el nonce a la cabecera script-src como ‘nonce-BASE64’.
  • Inyectar el atributo nonce en los scripts inline que controlas.

Ejemplo básico de generación y envío (simplificado):

add_action('send_headers', function() {
  $nonce = base64_encode(random_bytes(16));
  header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-$nonce' https://www.google-analytics.com; style-src 'self' 'nonce-$nonce' https://fonts.googleapis.com; img-src 'self' data: https:; frame-ancestors 'none';");
  // Guardar nonce para usarlo al imprimir scripts
  setcookie('csp-nonce', $nonce, 0, '/', '', is_ssl(), true);
});

// Al imprimir scripts inline en plantillas
$nonce = $_COOKIE['csp-nonce'] ?? '';
echo "";

Nota: usar cookies como ejemplo puede no ser ideal para todos los escenarios; es preferible almacenar el nonce en la request global o en la instancia de WP para que esté disponible cuando se impriman scripts y estilos.

Evitar conflictos con plugins

Muchos plugins inyectan inline scripts o cargan recursos desde CDNs. Para evitar roturas:

  • Identifica scripts de terceros con las herramientas de desarrollo del navegador.
  • Si no puedes evitar inline scripts, usa hashes SHA256 o nonces para permitirlos.
  • Considera sustituir plugins que dependen de inline JS por alternativas compatibles con CSP.

Usar hashes vs nonces

Ambas opciones permiten aprobar contenido inline sin habilitar ‘unsafe-inline’.

  • Hashes: calculas el SHA256 del contenido inline y lo agregas a script-src. Es estático y seguro cuando el contenido no cambia.
  • Nonces: son valores aleatorios por petición. Ideales cuando los scripts inline cambian entre peticiones o son generados dinámicamente.

Ejemplo de hash: ‘sha256-BASE64HASH’

Modo report-only y despliegue gradual

No despliegues una CSP restrictiva en producción sin pruebas. Usa report-only para recopilar violaciones sin bloquear recursos:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-abc'; report-uri https://mi-endpoint-de-reportes.example/csp-report

Recolecta reportes durante días, ajusta la política y luego cambia a enforcing. Para definir endpoints y formato de reportes consulta la documentación de MDN Web Docs y la guía de OWASP.

Reportes y monitoreo

Implementa un endpoint para recibir JSON con violaciones o usa servicios de terceros compatibles. También puedes integrar reportes con sistemas de logging internos. Revisa patrones frecuentes de bloqueo: recursos desde CDNs, analytics, fuentes web o endpoints API.

Pruebas y debugging

Pasos prácticos:

  • Habilita la política en modo report-only y navega por el sitio completo.
  • Revisa la consola del navegador y los reportes recibidos.
  • Ajusta directivas (añade dominios confiables o hashes/nonces).
  • Prueba en diferentes navegadores; el soporte de features y reportes puede variar.

Cloudflare ofrece guías prácticas para CSP y su integración con proxies; revisa su documentación cuando uses CDN o WAF en la ruta: Cloudflare Docs.

Errores comunes y soluciones

  • Problema: plugins que inyectan inline scripts. Solución: usar nonces/hashes o reemplazar plugin.
  • Problema: fuentes web bloqueadas. Solución: añadir orígenes en font-src y style-src.
  • Problema: analytics o pixel tracking bloqueado. Solución: permitir los dominios específicos en script-src o servir una versión sin inline.
  • Problema: políticas demasiado laxas con ‘unsafe-inline’. Solución: migrar gradualmente a nonces/hashes y reducir uso de inline.

Checklist rápida antes de producir

  • Recopilar lista de orígenes de scripts, estilos, imágenes, fuentes y conexiones.
  • Decidir entre hashes y nonces para contenido inline.
  • Probar en staging con report-only durante al menos una semana.
  • Revisar reportes, ajustar y desplegar política estricta en producción.
  • Monitorear después del despliegue para detectar regressions.

Recursos y lecturas recomendadas

Para profundizar en especificaciones y buenas prácticas revisa las guías oficiales en MDN Web Docs y las recomendaciones de OWASP. Si usas CDN o WAF, consulta la documentación concreta del proveedor en la que se expliquen cabeceras y caching.

Conclusión

Implementar Content Security Policy en cabeceras HTTP de WordPress eleva significativamente la seguridad y reduce el riesgo de XSS y fugas de datos. La clave está en aplicar un enfoque gradual: recopilar orígenes, usar report-only para pruebas, elegir entre nonces y hashes según el caso, y desplegar en servidor o vía WordPress según tus permisos. Una CSP bien diseñada protege usuarios sin romper funcionalidades y complementa buenas prácticas de hardening en servidores, actualizaciones y control de plugins.