La mayor parte de la seguridad de un sitio vive en su código, pero un pequeño conjunto de cabeceras de respuesta HTTP decide qué está dispuesto a hacer el navegador con ese código una vez que llega. Son la diferencia entre un <script> inyectado que se ejecuta y uno que el navegador se niega a ejecutar, entre una primera visita por HTTP sin cifrar y una que se actualiza antes de salir de la barra de direcciones.
Además son inusualmente baratas de desplegar. Las cabeceras son configuración, no código: casi todas son una sola línea en tu servidor web, CDN o framework, y se aplican a todas las respuestas a la vez. Esta guía cubre qué hace cada cabecera, cuáles valen la pena, cómo desplegar una Content-Security-Policy sin romper tu propio sitio, y cómo comprobar qué estás enviando hoy.
Por qué importan las cabeceras de seguridad
Una cabecera es una instrucción al navegador, y el navegador es la única parte que puede hacerla cumplir en la máquina del usuario. Eso convierte a las cabeceras en una segunda línea de defensa que se mantiene incluso cuando algo más arriba ya ha fallado:
- Contienen errores que ya has cometido. Una Content-Security-Policy no arregla un agujero de XSS, pero puede impedir que el script inyectado se ejecute o que envíe algo de vuelta.
- Cierran clases enteras de ataque. El clickjacking, la confusión de tipos MIME y la vuelta a HTTP no son fallos de tu código: son comportamientos del navegador de los que tienes que salirte explícitamente.
- Detienen fugas silenciosas de datos. Sin una política de referente, la URL completa de una página privada viaja a cada tercero del que cargas una imagen.
- Son lo primero que mira un auditor. Las cabeceras ausentes aparecen en todo escaneo automático, cuestionario de seguridad y revisión de proveedores, porque son el hallazgo más fácil que existe.
El problema es que es fácil dejarlas casi bien. Una CSP con unsafe-inline bloquea muy poco; una cabecera HSTS con una vida de un día no protege a casi nadie. El valor está en los detalles.
Las cabeceras que más importan
Seis cabeceras hacen casi todo el trabajo útil. El resto son heredadas, de alcance muy estrecho, o están superadas.
| Cabecera | Qué previene | Esfuerzo |
|---|---|---|
Content-Security-Policy | XSS, exfiltración de datos, código de terceros inesperado | Alto |
Strict-Transport-Security | Vuelta a HTTP, robo de cookies en la primera petición | Bajo |
X-Content-Type-Options | Que un archivo subido se interprete como script | Trivial |
Referrer-Policy | Filtrar URL completas a terceros | Trivial |
Permissions-Policy | Acceso a cámara, micrófono y geolocalización | Bajo |
Cross-Origin-Opener-Policy | Ataques entre ventanas desde páginas que abres | Bajo |
Empieza por abajo. Las cuatro cabeceras baratas son una línea cada una y casi nunca rompen nada, así que merece la pena desplegarlas hoy; la CSP es la que necesita un plan.
Content-Security-Policy
La CSP es una lista de permitidos que indica al navegador de dónde puede venir cada tipo de recurso. Una política razonablemente estricta se ve así:
default-src 'self'; script-src 'self' 'nonce-r4nd0m'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none'
Léela directiva por directiva. default-src 'self' es el valor de reserva: todo lo que no se nombre explícitamente solo puede cargarse desde tu propio origen. script-src es la que más importa, porque los scripts son lo que un atacante quiere ejecutar. frame-ancestors 'none' dice que ningún otro sitio puede poner tu página en un iframe, y es el sustituto moderno de X-Frame-Options. object-src 'none' y base-uri 'self' cierran dos rodeos antiguos que no cuesta nada tapar.
Dos valores deciden si tu política es real. 'unsafe-inline' en script-src permite cualquier <script> en línea, incluido el que haya inyectado un atacante, que es justo lo que la CSP existe para impedir. 'unsafe-eval' permite eval() y sus parientes. Una política con cualquiera de los dos sigue valiendo para las directivas que no son de script, pero no te está protegiendo del XSS.
La salida es un nonce o un hash. Un nonce es un valor aleatorio generado por respuesta y añadido tanto a la cabecera como a cada <script nonce="..."> legítimo; el navegador ejecuta solo los scripts que llevan ese valor, y un script inyectado no puede adivinarlo. Los hashes funcionan igual para scripts que nunca cambian. Si tu framework genera scripts en línea, es casi seguro que ya admite nonces; activarlo suele ser una opción de configuración.
Strict-Transport-Security
HSTS le dice al navegador que rechace el HTTP sin cifrar para tu dominio durante un periodo dado, lo que cierra el hueco que una redirección por sí sola deja abierto: la primerísima petición, antes de que llegue la redirección, viaja sin cifrar y puede interceptarse.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
El max-age va en segundos; un año es el objetivo habitual, y cualquier cosa por debajo de unos meses aporta poco. includeSubDomains extiende la regla a todos los subdominios, que es lo que la hace eficaz y también lo que conviene comprobar primero: cualquier subdominio que siga sirviéndose por HTTP dejará de funcionar. preload solicita la inclusión en la lista que los navegadores traen de serie, de modo que hasta la primera visita queda protegida.
Trata HSTS como algo difícil de revertir. Un navegador que haya visto una política de un año la hará cumplir durante un año, envíes lo que envíes después. Despliégala con un max-age corto, confirma que nada se rompió y luego súbelo. Y como HSTS solo importa si el certificado detrás es válido, comprueba el certificado y su caducidad con el verificador de certificados SSL antes de comprometerte con una política larga.
Las cabeceras pequeñas
Estas cuatro son una línea cada una y rara vez resultan polémicas:
X-Content-Type-Options: nosniff: impide que el navegador adivine el tipo de una respuesta a partir de su contenido. Sin ella, un archivo subido como.txtpuede ejecutarse como script si el navegador decide que lo parece.Referrer-Policy: strict-origin-when-cross-origin: envía la URL completa a tu propio origen, solo el origen a otros sitios HTTPS, y nada al bajar a HTTP. Es el valor moderno por defecto y evita que rutas y parámetros privados se filtren a terceros.Permissions-Policy: camera=(), microphone=(), geolocation=(): desactiva funciones del dispositivo que tu sitio no usa, tanto en tu página como en lo que incrustes.Cross-Origin-Opener-Policy: same-origin: corta la relación de ventana con las páginas que abres o que te abren, lo que bloquea una familia de ataques entre ventanas y es requisito de algunas funciones de aislamiento del navegador.
X-Frame-Options: DENY sigue teniendo sentido para navegadores antiguos, pero frame-ancestors en tu CSP lo sustituye y es más expresivo. Envía las dos: no entran en conflicto.
Cómo desplegar una CSP sin riesgos
Una CSP escrita a ojo bloqueará algo que tu propio sitio necesita. La vía de «informar primero» lo evita por completo:
- Despliega hoy las cuatro cabeceras baratas.
nosniff, una política de referente, una política de permisos y HSTS con unmax-agecorto. Normalmente ninguna rompe un sitio que funciona. - Añade
Content-Security-Policy-Report-Only. Sintaxis idéntica, pero el navegador solo informa de las violaciones en lugar de bloquearlas. Tu sitio se comporta exactamente como antes. - Recoge informes durante una o dos semanas. Nombrarán todos los scripts de terceros, tipografías, puntos de analítica y manejadores en línea que habías olvidado. Es el paso que la gente se salta, y el que hace indoloro todo lo demás.
- Ajusta hasta que los informes queden en silencio. Añade las fuentes que realmente necesitas y quita las que descubras que no. Resiste la tentación de ampliar una directiva a
*para silenciar un solo informe. - Pasa a modo de aplicación. Renombra la cabecera a
Content-Security-Policy. Mantén la versión de solo informe al lado para cuando quieras probar una política más estricta. - Quita
unsafe-inlineal final. Este es el trabajo de verdad: mover los scripts en línea a archivos o darles nonces. Todo lo anterior es preparación; este es el paso que detiene el XSS.
Una nota práctica: las cabeceras son por respuesta, así que configúralas en el borde —tu CDN, proxy inverso o middleware del framework— y no página por página. Hacerlo en un único lugar es también la única forma de asegurarte de que tus páginas de error y tus redirecciones también las llevan, que es justo donde suelen faltar.
Cabeceras que puedes dejar de enviar
Algunos consejos han envejecido mal, y enviar una cabecera muerta no es inocuo: añade bytes y hace que una auditoría parezca terminada cuando no lo está.
X-XSS-Protection: los filtros del navegador que controlaba ya no existen. No hace nada en ningún navegador actual, y el antiguo valormode=blockllegó a introducir sus propios problemas. Quítala y confía en la CSP.Public-Key-Pins: HPKP se ha retirado de los navegadores. Era fácil quedarte fuera de tu propio dominio, y por eso se abandonó.Expect-CT: la transparencia de certificados se aplica ya por defecto, así que la cabecera es redundante.Feature-Policy: renombrada aPermissions-Policy, con otra sintaxis. Envía la nueva; conserva la antigua solo si tienes que dar soporte a algo muy viejo.
Por el contrario, algo que sí conviene quitar es la información que regalas: una cabecera Server o X-Powered-By que nombre la versión exacta de tu framework le dice al atacante qué exploits probar primero. No es una vulnerabilidad, pero no hay razón para publicarlo.
Cómo comprobar tus propias cabeceras
Las cabeceras son públicas, así que comprobar la configuración de cualquier sitio lleva segundos:
curl -I https://example.com. Una peticiónHEADimprime todas las cabeceras de respuesta sin descargar la página. Añade-Lpara inspeccionar la URL final en lugar de una redirección.- El panel de red del navegador. Abre las herramientas de desarrollo, recarga, selecciona el documento principal y lee la sección de cabeceras de respuesta. La consola también registra cada violación de CSP con la directiva que la bloqueó, que es la forma más rápida de depurar una política.
- Un verificador que puntúe el resultado. Nuestro verificador de cabeceras de seguridad consulta una URL e indica qué cabeceras están presentes, cuáles faltan y dónde una cabecera presente es más débil de lo que parece.
Comprueba más que tu portada. A menudo las cabeceras se configuran en la aplicación principal pero no en los hosts de recursos estáticos, las páginas de error, los subdominios de API o la redirección que lleva de www al dominio raíz. Ya que estás, confirma que la propia cadena de redirecciones es correcta; la referencia de códigos de estado HTTP explica qué código corresponde a una mudanza permanente.
Para escribir una política sin memorizar la sintaxis, parte de nuestro generador de CSP, que además señala los ajustes que la debilitan.
Errores habituales que evitar
- Una CSP con
unsafe-inlineenscript-src. Pasa casi todas las comprobaciones automáticas y no detiene casi ningún XSS. Usa un nonce o hashes. - Ampliar una directiva a
*para arreglar un informe. Añade en su lugar el origen concreto;*renuncia a la directiva por completo. - HSTS con un
max-agelargo desde el primer día. Es irreversible en la práctica durante la vida que hayas publicado. Empieza corto. includeSubDomainsantes de auditar los subdominios. Todo lo que siga en HTTP, incluidas las herramientas internas, deja de cargar.- Cabeceras en la aplicación pero no en el borde. Las páginas de error, las redirecciones y los hosts de recursos quedan sin protección, y son justo las respuestas a las que acuden los atacantes.
- Enviar la misma cabecera dos veces. Dos cabeceras CSP se intersecan, no se fusionan, así que el resultado es más estricto que cualquiera de las dos y suele romper algo; dos valores de
X-Frame-Optionsen conflicto pueden ignorarse por completo. - Seguir enviando
X-XSS-Protectiony darlo por hecho. Una cabecera muerta da una falsa tranquilidad en un informe. - No pasar nunca del modo de solo informe. Los informes que no se atienden no protegen a nadie.
Comprueba tus cabeceras
Las cabeceras de seguridad ofrecen la mejor relación entre protección y esfuerzo que hay en la web: cuatro de ellas son una sola línea cada una, y la quinta, la CSP, consiste sobre todo en escuchar tus propios informes de violaciones antes de aplicar nada. Empieza mirando qué envía tu sitio hoy, añade de inmediato las cabeceras baratas, y luego pon una CSP en modo de solo informe y deja que te diga qué debe decir tu política.
Herramientas y guías relacionadas:
- Verificador de cabeceras de seguridad: comprueba qué cabeceras envía una URL y cuáles faltan.
- Verificador de certificados SSL: comprueba el certificado detrás de tu política HSTS.
- Códigos de estado HTTP: asegúrate de que tu redirección a HTTPS devuelve el código correcto.
- Búsqueda de DNS: guía completa: los registros detrás de cada nombre de host de tu política.
Preguntas frecuentes
¿Qué cabeceras de seguridad debo añadir primero?⌄
Empieza por las cuatro que casi nunca rompen nada: X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, una Permissions-Policy que desactive las funciones de dispositivo que no uses, y Strict-Transport-Security con un max-age corto que subirás más adelante. Luego añade una Content-Security-Policy en modo de solo informe, porque es la que necesita observación antes de aplicarse.
¿Sirve de algo una CSP con unsafe-inline?⌄
Sirve para todo excepto para el ataque que la CSP detiene mejor. Directivas como img-src, connect-src, frame-ancestors y object-src siguen limitando a dónde pueden ir los datos y quién puede enmarcarte. Pero 'unsafe-inline' en script-src permite que se ejecute cualquier script en línea inyectado, así que la política no te protege del XSS. Sustitúyelo por un nonce por respuesta o por hashes de script.
¿Es riesgoso Strict-Transport-Security?⌄
Es riesgoso sobre todo porque es difícil de deshacer: un navegador que vio max-age=31536000 rechazará el HTTP sin cifrar para tu dominio durante un año, sin importar lo que envíes después. Despliégalo primero con un max-age corto, confirma que todos los subdominios son accesibles por HTTPS antes de añadir includeSubDomains, y solo entonces sube la duración y considera preload.
¿Qué sustituyó a X-Frame-Options y X-XSS-Protection?⌄
La directiva frame-ancestors de Content-Security-Policy sustituye a X-Frame-Options y es más expresiva, aunque enviar ambas es inocuo y sigue ayudando a navegadores antiguos. X-XSS-Protection no tiene sustituto porque los filtros del navegador que controlaba se eliminaron; la CSP es lo que detiene el XSS ahora, y la cabecera antigua se puede quitar sin más.
¿Dónde debo configurar las cabeceras de seguridad?⌄
En el borde y en un solo lugar: tu CDN, proxy inverso o middleware del framework. Configurarlas página por página deja sin cubrir las páginas de error, las redirecciones, los hosts de recursos y las respuestas de la API, y son justo las respuestas que se olvidan en una auditoría y a las que se acude en un ataque.
¿Cómo compruebo qué cabeceras envía mi sitio?⌄
Ejecuta curl -I https://tudominio.com añadiendo -L para ver el destino final en lugar de una redirección, o abre el panel de red del navegador y lee las cabeceras de respuesta del documento principal. El verificador de cabeceras de seguridad lo hace desde tu navegador y señala tanto las cabeceras ausentes como las presentes que son más débiles de lo que parecen.
Más de 15 años de experiencia práctica en desarrollo web y seguridad. Centrado en la seguridad web práctica: mantener los sitios seguros, fiables y fáciles de diagnosticar. BuildToolForge existe para que estas comprobaciones sean gratuitas, privadas y comprensibles. Las herramientas se ejecutan en tu navegador siempre que es posible, y nada de lo que introduces en una herramienta se guarda.
Cómo escribimos nuestras guías: Cada guía nace de la experiencia práctica, se contrasta con los estándares oficiales y se prueba con nuestras propias herramientas en dominios reales. Actualizamos las guías cuando cambian los estándares o las herramientas. ¿Has visto un error? Escríbenos a contact@buildtoolforge.com.