Este documento describe cómo configurar Content Security Policy (CSP) para proteger la aplicación contra ataques XSS y de inyección de datos.
Content Security Policy (CSP) es una capa adicional de seguridad que ayuda a detectar y mitigar ciertos tipos de ataques, incluyendo Cross Site Scripting (XSS) y ataques de inyección de datos. CSP proporciona un conjunto de headers HTTP estándar que permiten declarar fuentes aprobadas de contenido que los navegadores deben permitir cargar en la página.
La aplicación ya está configurada con CSP para desarrollo:
- Vite Dev Server: Los headers CSP se configuran automáticamente en
vite.config.ts - Meta Tags: Se incluyen meta tags CSP en
index.htmlcomo respaldo
La política CSP actual permite:
- Scripts: Mismo origen, inline scripts, eval (necesario para Vue en desarrollo), y blob URLs
- Estilos: Mismo origen e inline styles (necesario para Tailwind CSS)
- Imágenes: Mismo origen, data URLs, blob URLs, y HTTPS
- Fuentes: Mismo origen y data URLs
- Conexiones: Mismo origen, API backend, Microsoft OAuth endpoints, y blob URLs
- Media: Mismo origen, blob URLs, y data URLs
- Objetos: Ninguno (bloqueado)
- Base URI: Solo mismo origen
- Form Actions: Mismo origen, API backend, y Microsoft OAuth endpoints
- Frame Ancestors: Ninguno (previene clickjacking)
- Upgrade Insecure Requests: Fuerza HTTPS
- Block Mixed Content: Bloquea contenido mixto
La aplicación está protegida contra ataques ClickJacking mediante dos mecanismos complementarios:
- X-Frame-Options: DENY - Header HTTP que previene que la página sea embebida en frames/iframes
- Content-Security-Policy: frame-ancestors 'none' - Directiva CSP moderna que también previene el embedding
- ✅ Desarrollo: Headers configurados en
vite.config.ts(líneas 86, 97) - ✅ HTML: Meta tags en
index.htmlcomo respaldo (línea 14) - ✅ CSP:
frame-ancestors 'none'incluido en la política CSP
- DENY: La página nunca puede ser mostrada en un frame (recomendado para máxima seguridad)
- SAMEORIGIN: La página solo puede ser mostrada en un frame del mismo origen
- ALLOW-FROM uri: Permite mostrar la página en un frame desde un origen específico (deprecated)
Nota: La aplicación usa DENY para máxima protección. Si necesitas permitir embedding desde tu mismo dominio, cambia a SAMEORIGIN.
Los headers HTTP tienen prioridad sobre los meta tags. En producción, debes configurar los headers CSP y X-Frame-Options en tu servidor web (nginx, Apache, etc.) en lugar de depender solo de los meta tags.
CRÍTICO: Asegúrate de que tu servidor web en producción incluya ambos headers:
X-Frame-Options: DENY(oSAMEORIGINsi necesitas embedding del mismo origen)Content-Security-Policy: ... frame-ancestors 'none' ...(oframe-ancestors 'self'si necesitas embedding del mismo origen)
Sin estos headers en producción, la aplicación será vulnerable a ataques ClickJacking.
Agrega los siguientes headers en tu configuración de Nginx:
server {
listen 443 ssl http2;
server_name tu-dominio.com;
# ... otras configuraciones SSL ...
# Content Security Policy
# Ajusta las URLs según tu configuración
set $api_url "https://api.tu-dominio.com";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' $api_url https://login.microsoftonline.com https://login.live.com https://graph.microsoft.com blob:; media-src 'self' blob: data:; object-src 'none'; base-uri 'self'; form-action 'self' $api_url https://login.microsoftonline.com https://login.live.com; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content" always;
# Headers de seguridad - IMPORTANTE: X-Frame-Options previene ClickJacking
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always; # Protección contra ClickJacking
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# ... resto de la configuración ...
location / {
root /var/www/securis-web/dist;
try_files $uri $uri/ /index.html;
}
}Agrega los siguientes headers en tu configuración de Apache (.htaccess o configuración del servidor):
<IfModule mod_headers.c>
# Content Security Policy
# Ajusta las URLs según tu configuración
Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https://api.tu-dominio.com https://login.microsoftonline.com https://login.live.com https://graph.microsoft.com blob:; media-src 'self' blob: data:; object-src 'none'; base-uri 'self'; form-action 'self' https://api.tu-dominio.com https://login.microsoftonline.com https://login.live.com; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content"
# Headers de seguridad - IMPORTANTE: X-Frame-Options previene ClickJacking
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY" # Protección contra ClickJacking
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>Si estás usando Express para servir la aplicación:
const express = require("express");
const app = express();
// Content Security Policy
const apiUrl = process.env.API_URL || "https://api.tu-dominio.com";
const cspPolicy = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob: https:",
"font-src 'self' data:",
`connect-src 'self' ${apiUrl} https://login.microsoftonline.com https://login.live.com https://graph.microsoft.com blob:`,
"media-src 'self' blob: data:",
"object-src 'none'",
"base-uri 'self'",
`form-action 'self' ${apiUrl} https://login.microsoftonline.com https://login.live.com`,
"frame-ancestors 'none'",
"upgrade-insecure-requests",
"block-all-mixed-content",
].join("; ");
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", cspPolicy);
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY"); // Protección contra ClickJacking
res.setHeader("X-XSS-Protection", "1; mode=block");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
res.setHeader(
"Permissions-Policy",
"geolocation=(), microphone=(), camera=()"
);
next();
});
// Servir archivos estáticos
app.use(express.static("dist"));-
'unsafe-inline' y 'unsafe-eval':
- En desarrollo, estos son necesarios para Vue y algunas librerías
- En producción, considera usar nonces para scripts y estilos inline
- Vue 3 en modo producción no debería necesitar 'unsafe-eval'
-
Nonces:
- Para mayor seguridad, implementa nonces para scripts y estilos inline
- Requiere configuración del servidor para generar nonces únicos por request
-
Reporting:
- Considera agregar
report-urioreport-topara recibir reportes de violaciones CSP - Útil para detectar y corregir problemas en producción
- Considera agregar
# Generar nonce único por request
set $nonce $request_id;
# Usar nonce en CSP
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$nonce' blob:; style-src 'self' 'nonce-$nonce'; ..." always;Luego, en tu HTML, usa el nonce:
<script nonce="{{ $nonce }}">
// Tu código
</script>-
Browser DevTools:
- Abre las DevTools (F12)
- Ve a la pestaña "Console"
- Busca violaciones CSP (aparecerán como errores)
-
Online CSP Evaluator:
- Usa herramientas como CSP Evaluator
- Ingresa tu política CSP para validarla
-
Headers HTTP:
- Usa herramientas como
curlo extensiones del navegador - Verifica que los headers CSP estén presentes:
curl -I https://tu-dominio.com
- Usa herramientas como
-
Errores de CSP en la consola:
- Revisa qué recursos están siendo bloqueados
- Ajusta la política CSP para permitir esos recursos si son legítimos
- Verifica que las URLs estén correctamente configuradas
-
Microsoft OAuth no funciona:
- Asegúrate de que
https://login.microsoftonline.comyhttps://login.live.comestén enconnect-src - Verifica que
form-actionincluya estos dominios
- Asegúrate de que
-
PDFs no se cargan:
- Verifica que
blob:esté permitido enconnect-src,media-src, eimg-src - Asegúrate de que
data:esté permitido si usas base64
- Verifica que
-
Estilos no se aplican:
- Verifica que
style-srcincluya'unsafe-inline'o usa nonces - Tailwind CSS puede necesitar inline styles
- Verifica que
-
Error de ClickJacking detectado:
- Verifica que
X-Frame-Optionsesté configurado en tu servidor web - Verifica que
frame-ancestorsesté en la política CSP - Usa
curl -I https://tu-dominio.compara verificar los headers - Asegúrate de que los headers se apliquen a todas las rutas, no solo a la raíz
- En Nginx, usa
alwaysdespués deadd_headerpara asegurar que se aplique incluso en errores
- Verifica que
# Verificar que los headers estén presentes
curl -I https://tu-dominio.com
# Debes ver:
# X-Frame-Options: DENY
# Content-Security-Policy: ... frame-ancestors 'none' ...-
Browser DevTools:
- Abre las DevTools (F12)
- Ve a la pestaña "Network"
- Recarga la página
- Haz clic en cualquier request
- Verifica los headers de respuesta
-
Online Security Headers Checker:
- Usa herramientas como SecurityHeaders.com
- Ingresa tu URL para verificar todos los headers de seguridad
-
Prueba Manual:
<!-- Intenta embebar tu página en un iframe - debería fallar --> <iframe src="https://tu-dominio.com"></iframe>