Content-Security-Policy: la primera línea de defensa contra XSS
El Content-Security-Policy (CSP) es el header más poderoso y complejo. Define de dónde puede tu página cargar recursos (scripts, estilos, imágenes, fuentes). Ejemplo básico: default-src 'self' solo permite recursos del mismo origin. Para permitir CDNs específicos: script-src 'self' https://cdn.example.com. El error más común es usar 'unsafe-inline' y 'unsafe-eval', que eliminan gran parte de la protección contra XSS.
Estrategia recomendada: empezá con CSP-Report-Only durante desarrollo para ver qué se rompe sin bloquear nada. Monitoreá los reportes (configurá report-uri /csp-endpoint) y ajustá las directivas hasta que no haya violations legítimas. Entonces cambiá a Content-Security-Policy enforcement. Para sitios con inline scripts/styles legacy, usá nonces: generá un token random por request y agregalo como atributo nonce="random123" en cada <script>, permitiéndolo con script-src 'nonce-random123'.
Directivas críticas: default-src (fallback para todo), script-src (de dónde cargar JS), style-src (CSS), img-src (imágenes), connect-src (fetch/XHR/WebSocket), frame-ancestors (quién puede embeber tu página en iframe, reemplaza X-Frame-Options), upgrade-insecure-requests (auto-upgrade HTTP a HTTPS), block-all-mixed-content (bloquea HTTP en páginas HTTPS). Un CSP bien configurado previene 99% de XSS attacks incluso si tenés vulnerabilidades en el código.
HSTS: forzando HTTPS permanentemente
Strict-Transport-Security (HSTS) le dice al browser: 'siempre usá HTTPS para este dominio, nunca HTTP, ni siquiera si el usuario tipea http:// explícitamente'. Formato: max-age=31536000; includeSubDomains; preload. El max-age en segundos indica cuánto el browser recordará esta regla (31536000 = 1 año). includeSubDomains aplica la regla a todos los subdominios (crítico, pero asegurate de que todos soporten HTTPS).
La directiva preload es opcional pero poderosa: somete tu dominio a la HSTS Preload List de Chromium (compartida por Chrome, Firefox, Safari, Edge). Esto significa que browsers NUNCA intentarán HTTP en tu dominio, ni siquiera en la primera visita. Beneficio: elimina window de ataque en first connection. Riesgo: si rompés HTTPS en algún subdominio, usuarios no podrán acceder hasta que lo arregles. Requisitos para preload: max-age mínimo 1 año, includeSubDomains presente, HTTPS funcionando en todos los subdominios.
Errores comunes: setear HSTS sin tener HTTPS configurado (el header se ignora en HTTP), usar max-age muy corto en producción (menos de 6 meses no es efectivo), olvidarse de includeSubDomains y dejar subdominios vulnerables. Estrategia de rollout seguro: empezá con max-age=300 (5 minutos), monitoreá por días, aumentá gradualmente a 86400 (1 día), luego a 31536000 (1 año). Si todo funciona, considerá preload.
Headers de aislamiento: COEP, COOP, CORP
Los Cross-Origin headers (COEP, COOP, CORP) trabajan juntos para crear aislamiento fuerte entre origins, necesario para features como SharedArrayBuffer. COEP (Cross-Origin-Embedder-Policy) require-corp dice: 'solo cargá recursos cross-origin si tienen header CORP o CORS apropiado'. Esto previene que tu página lea datos de otros origins sin permiso explícito. Necesario para usar APIs poderosas como performance.measureUserAgentSpecificMemory().
COOP (Cross-Origin-Opener-Policy) same-origin previene que ventanas popup o tabs abiertos por tu sitio puedan acceder al window.opener. Protege contra ataques tipo 'reverse tabnabbing' donde un sitio malicioso abierto desde tu app puede manipular la página original. same-origin-allow-popups es más permisivo: permite popups pero no cross-origin. unsafe-none es el default (sin protección).
CORP (Cross-Origin-Resource-Policy) controla quién puede cargar TUS recursos. same-origin: solo tu origin. same-site: tu site y subdominios. cross-origin: cualquiera (útil para CDNs públicos). Estos tres headers combinados crean 'cross-origin isolation', habilitando features sensibles de performance. Si tus resources (imágenes, scripts) son embebidos en otros sitios legítimamente, necesitás CORP: cross-origin o CORS headers apropiados.
Configuración por plataforma: Nginx, Apache, Node.js
En Nginx, agregá headers en el bloque server o location: add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;. La directiva always es crucial: sin ella, Nginx no envía el header en respuestas de error (4xx, 5xx). Para CSP multilinea, escapá o usá una variable. Ejemplo: set $csp "default-src 'self'; script-src 'self' https://cdn.com"; luego add_header Content-Security-Policy $csp always;.
En Apache, usá mod_headers. En .htaccess o config: Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload". Para CSP: Header always set Content-Security-Policy "default-src 'self'; script-src 'self'". Si tenés problemas, verificá que mod_headers esté habilitado: a2enmod headers y restart Apache.
En Node.js/Express, usá middleware como helmet: app.use(helmet()) setea varios headers seguros por default. Para customizar: app.use(helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'nonce-..."] } })). Para nonces dinámicos: generá con crypto.randomBytes(16).toString('base64') en cada request, guardalo en res.locals.nonce, y referencialo en CSP y templates. En Next.js, configurá headers en next.config.js bajo async headers() return.