Cabeceras de seguridad HTTP
Las cabeceras HTTP de seguridad son instrucciones que el servidor envía al navegador junto con la respuesta para indicarle cómo debe comportarse al manejar el contenido de la página. Establecen una capa de protección adicional frente a ataques como XSS, clickjacking, inyección de contenido, etc.
Son una de las medidas más fáciles y eficaces de implementar, pero también de las más olvidadas.
Principales cabeceras de seguridad
Content-Security-Policy (CSP)
Es la cabecera de seguridad más potente. Define qué recursos (scripts, estilos, imágenes, fuentes, etc.) puede cargar el navegador y desde qué orígenes. Si un script intenta ejecutarse desde un origen no autorizado, el navegador lo bloquea.
Protege contra:
- XSS
- Inyección de contenido
Ejemplo:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.miweb.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'
Directivas más importantes de Content-Security-Policy:
| Directiva | Qué controla |
|---|---|
default-src | Política por defecto para todos los recursos |
script-src | Fuentes de JavaScript |
style-src | Fuentes de CSS |
img-src | Fuentes de imágenes |
connect-src | Orígenes permitidos en fetch, XHR, WebSocket |
font-src | Fuentes tipográficas |
frame-ancestors | Qué páginas pueden embeber esta en un iframe |
form-action | Dónde pueden enviarse los formularios |
Valores especiales:
| Valor | Significado |
|---|---|
'self' | Solo el propio dominio |
'none' | Ningún origen |
'unsafe-inline' | Permite código inline (evitar si es posible) |
'unsafe-eval' | Permite eval() (evitar) |
'nonce-<valor>' | Permite scripts con ese nonce específico |
En Express con Helmet:
import helmet from "helmet";
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.miweb.com"],
frameAncestors: ["'none'"],
formAction: ["'self'"],
}
}));
CSP Evaluator es una herramienta útil para generar y probar tu CSP.
Strict-Transport-Security (HSTS)
Indica al navegador que siempre use HTTPS para comunicarse con este dominio, incluso si el usuario escribe http://. Una vez recibida, el navegador no permitirá conexiones HTTP durante el periodo indicado.
Protege contra:
- Ataques Man-in-the-Middle
- Downgrade attacks
- SSL stripping
Ejemplo:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
| Parámetro | Descripción |
|---|---|
max-age | Segundos que el navegador recuerda usar HTTPS (31536000 = 1 año) |
includeSubDomains | Aplica también a todos los subdominios |
preload | Solicita inclusión en la lista HSTS preload de los navegadores |
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}));
Activar HSTS sólo cuando el certificado SSL esté correctamente configurado. Una vez activado con preload, es muy difícil volver atrás.
X-Content-Type-Options
Impide que el navegador intente "adivinar" el tipo MIME de un recurso cuando el servidor ya lo ha declarado. Sin esta cabecera, algunos navegadores pueden ejecutar un fichero .txt como JavaScript si su contenido parece código.
Protege contra:
- MIME-type sniffing.
- Ataques de confusión de tipos.
Ejemplo:
X-Content-Type-Options: nosniff
app.use(helmet.noSniff()); // Incluido en helmet() por defecto
X-Frame-Options
Controla si la página puede ser incrustada en un <iframe>, <frame> o <embed>.
Protege contra:
- Ataques de clickjacking, donde un atacante superpone un
iframeinvisible sobre su página para robar clics del usuario.
Ejemplo:
X-Frame-Options: DENY # Nadie puede embeber esta página
X-Frame-Options: SAMEORIGIN # Solo el mismo dominio puede embeber
app.use(helmet.frameguard({ action: 'deny' }));
La directiva frame-ancestors en CSP es la alternativa moderna y más flexible. Se recomienda usar ambas para mayor compatibilidad.
Referrer-Policy
Controla qué información se incluye en la cabecera Referer al navegar a otro sitio. Por defecto, los navegadores pueden enviar la URL completa de la página origen, lo que puede filtrar información sensible (tokens en URL, rutas internas…).
Protege contra filtrado de información sensible en URLs.
Referrer-Policy: no-referrer-when-downgrade # Por defecto en muchos navegadores
Referrer-Policy: strict-origin-when-cross-origin # Recomendado
Referrer-Policy: no-referrer # Nunca envía el referrer
app.use(helmet.referrerPolicy({
policy: 'strict-origin-when-cross-origin'
}));
Permissions-Policy (antes Feature-Policy)
Define qué APIs del navegador puede usar la página (cámara, micrófono, geolocalización, notificaciones…). Evita que scripts de terceros accedan a funcionalidades sensibles.
Protege contra el abuso de APIs del navegador por parte de scripts de terceros.
Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=()
app.use(
helmet.permittedCrossDomainPolicies(),
);
// O manualmente:
app.use((req, res, next) => {
res.setHeader(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=(self)'
);
next();
});
Cross-Origin-Opener-Policy (COOP)
Aísla el contexto de navegación de la página frente a ventanas abiertas desde otros orígenes, previniendo que páginas maliciosas abiertas desde la tuya puedan acceder a tu window.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy (CORP)
Indica qué orígenes pueden cargar los recursos de esta respuesta. Evita que otros sitios puedan cargar tus imágenes, scripts u otros recursos.
Cross-Origin-Resource-Policy: same-origin
Cross-Origin-Resource-Policy: same-site
Cross-Origin-Resource-Policy: cross-origin # Permite cualquier origen
Helmet: todas las cabeceras de un vistazo
Helmet es el middleware de facto para gestionar cabeceras de seguridad en Express. Con una sola línea activa las cabeceras más importantes con valores seguros por defecto.
npm install helmet
import express from 'express';
import helmet from "helmet";
const app = express();
// Configuración recomendada para producción
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
frameAncestors: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));
Cabeceras que activa Helmet por defecto:
Content-Security-Policy: default-src 'self'; ...
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-XSS-Protection: 0
Verificar las cabeceras de tu aplicación
Puedes comprobar qué cabeceras de seguridad tiene tu aplicación con estas herramientas:
- securityheaders.com — Análisis y puntuación de cabeceras.
- observatory.mozilla.org — Análisis completo de seguridad web de Mozilla.
- Herramientas de desarrollador del navegador — Pestaña
Network→ selecciona una petición →Response Headers.