Cómo implementar la CSP y la depuración de Trusted Types en Herramientas para desarrolladores de Chrome

Kateryna Prokopenko
Kateryna Prokopenko
Alfonso Castaño
Alfonso Castaño

En esta entrada de blog, se describe la implementación de la compatibilidad de DevTools para depurar problemas de la política de seguridad del contenido (CSP) con la ayuda de la pestaña Problemas que se presentó recientemente.

El trabajo de implementación se realizó durante 2 pasantías: 1. Durante la primera, creamos el framework de informes generales y diseñamos los mensajes de problemas para 3 problemas de incumplimiento del CSP. 2. Durante la segunda, agregamos problemas de tipos de confianza junto con algunas funciones especializadas de DevTools para la depuración de tipos de confianza.

¿Qué es una política de seguridad del contenido?

La política de seguridad del contenido (CSP) permite restringir ciertos comportamientos en un sitio web para aumentar la seguridad. Por ejemplo, la CSP se puede usar para inhabilitar las secuencias de comandos intercaladas o eval, lo que reduce la superficie de ataque de los ataques de secuencia de comandos entre sitios (XSS). Para obtener una introducción detallada a los CSP, consulta este artículo.

Un CSP particularmente nuevo es la política de Tipos de confianza(TT), que habilita un análisis dinámico que puede evitar de forma sistemática una gran clase de ataques de inyección en sitios web. Para lograrlo, TT ayuda a un sitio web a controlar su código JavaScript para permitir que solo se asignen ciertos tipos de elementos a los sumideros de DOM, como innerHTML.

Un sitio web puede activar una política de seguridad del contenido si incluye un encabezado HTTP en particular. Por ejemplo, el encabezado content-security-policy: require-trusted-types-for 'script'; trusted-types default activa la política de TT para una página.

Cada política puede funcionar en uno de estos modos:

  • modo de aplicación forzosa: En este modo, cada incumplimiento de política es un error.
  • Modo solo de informes: Informa el mensaje de error como una advertencia, pero no causa una falla en la página web.

Implementa problemas de la política de seguridad del contenido en la pestaña Problemas

El objetivo de este trabajo era mejorar la experiencia de depuración de los problemas de CSP. Cuando considera problemas nuevos, el equipo de DevTools sigue aproximadamente este proceso:

  1. Definir historias de usuario Identifica un conjunto de historias de usuario en el frontend de DevTools que cubra cómo un desarrollador web debería investigar el problema.
  2. Implementación del frontend: En función de las historias de usuario, identifica qué datos son necesarios para investigar el problema en el frontend (p.ej., una solicitud relacionada, el nombre de una cookie, una línea en una secuencia de comandos o un archivo HTML, etcétera).
  3. Detección de problemas. Identifica los lugares del navegador en los que se puede detectar el problema en Chrome y, luego, instrumenta el lugar para informar un problema, incluida la información relevante del paso 2.
  4. Guarda y muestra los problemas. Almacena los problemas en un lugar adecuado y haz que estén disponibles para DevTools una vez que se abra.
  5. Diseña el texto de los problemas. Crea un texto explicativo que ayude al desarrollador web a comprender y, lo que es más importante, solucionar el problema.

Paso 1: Define historias de usuario para los problemas de CSP

Antes de comenzar con nuestro trabajo de implementación, creamos un documento de diseño con historias de usuario para comprender mejor lo que debíamos hacer. Por ejemplo, escribimos la siguiente historia de usuario:


Como desarrollador, que acaba de darse cuenta de que algunas partes de mi sitio web están bloqueadas, quiero hacer lo siguiente:- - …averiguar si la CSP es un motivo por el que se bloquean los iframes o las imágenes en mi sitio web - …aprender qué directiva de CSP causa el bloqueo de un recurso determinado - …saber cómo cambiar la CSP de mi sitio web para permitir la visualización de recursos bloqueados actualmente o la ejecución de JS bloqueados actualmente.


Para explorar esta historia de usuario, creamos algunas páginas web de ejemplo simples que mostraban los incumplimientos del CSP que nos interesaban y las exploramos para familiarizarnos con el proceso. Estas son algunas de las páginas web de ejemplo (abre la demostración con la pestaña Problemas abierta):

Con este proceso, descubrimos que la ubicación de origen era la información más importante para depurar los problemas del CSP. También nos resultó útil encontrar rápidamente el iframe y la solicitud asociados en caso de que se bloqueara un recurso, y que un vínculo directo al elemento HTML en el panel Elementos de DevTools también podría ser útil.

Paso 2: Implementación del frontend

Transformamos esta estadística en el primer borrador de la información que queríamos poner a disposición de DevTools a través del Protocolo de herramientas para desarrolladores de Chrome (CDP):

A continuación, se muestra el extracto de third_party/blink/public/devtools_protocol/browser_protocol.pdl.

 type ContentSecurityPolicyIssueDetails extends object
   properties
     # The url not included in allowed sources.
     optional string blockedURL
     # Specific directive that is violated, causing the CSP issue.
     string violatedDirective
     boolean isReportOnly
     ContentSecurityPolicyViolationType contentSecurityPolicyViolationType
     optional AffectedFrame frameAncestor
     optional SourceCodeLocation sourceCodeLocation
     optional DOM.BackendNodeId violatingNodeId

La definición anterior codifica, en esencia, una estructura de datos JSON. Está escrito en un lenguaje simple llamado PDL (lenguaje de datos de protocolo). La PDL se usa para dos fines. Primero, usamos PDL para generar las definiciones de TypeScript en las que se basa el frontend de DevTools. Por ejemplo, la definición de PDL anterior genera la siguiente interfaz de TypeScript:

export interface ContentSecurityPolicyIssueDetails {
  /**
  * The url not included in allowed sources.
  */
  blockedURL?: string;
  /**
  * Specific directive that is violated, causing the CSP issue.
  */
  violatedDirective: string;
  isReportOnly: boolean;
  contentSecurityPolicyViolationType: ContentSecurityPolicyViolationType;
  frameAncestor?: AffectedFrame;
  sourceCodeLocation?: SourceCodeLocation;
  violatingNodeId?: DOM.BackendNodeId;
}

En segundo lugar, y lo que es más importante, generamos una biblioteca de C++ a partir de la definición que controla la generación y el envío de estas estructuras de datos desde el backend de Chromium C++ al frontend de DevTools. Con esa biblioteca, se puede crear un objeto ContentSecurityPolicyIssueDetails con el siguiente código C++:

protocol::Audits::ContentSecurityPolicyIssueDetails::create()
  .setViolatedDirective(d->violated_directive)
  .setIsReportOnly(d->is_report_only)
  .setContentSecurityPolicyViolationType(BuildViolationType(
      d->content_security_policy_violation_type)))
  .build();

Una vez que determinamos qué información queríamos poner a disposición, tuvimos que explorar dónde obtenerla de Chromium.

Paso 3: Detección de problemas

Para que la información esté disponible para el Protocolo de DevTools de Chrome (CDP) en el formato descrito en la última sección, necesitábamos encontrar el lugar donde la información estaba realmente disponible en el backend. Por fortuna, el código de la CSP ya tenía un cuello de botella que se usaba para el modo solo de informes, en el que podíamos conectarnos: ContentSecurityPolicy::ReportViolation informa problemas a un extremo de informes (opcional) que se puede configurar en el encabezado HTTP de la CSP. La mayor parte de la información que queríamos informar ya estaba disponible, por lo que no fue necesario realizar grandes cambios en el backend para que funcionara nuestra instrumentación.

Paso 4: Guarda y muestra los problemas

Una pequeña complicación es que también queríamos informar los problemas que ocurrieron antes de abrir DevTools, de manera similar a como se manejan los mensajes de la consola. Esto significa que no informamos los problemas de inmediato al frontend, sino que usamos un almacenamiento que se propaga con problemas independientemente de si DevTools está abierto o no. Una vez que se abre DevTools (o se conecta cualquier otro cliente de CDP), todos los problemas registrados anteriormente se pueden volver a reproducir desde el almacenamiento.

Esto concluyó el trabajo del backend y ahora debíamos enfocarnos en cómo mostrar el problema en el frontend.

Paso 5: Diseña el texto de los problemas

El diseño del texto de los problemas es un proceso que involucra a varios equipos además del nuestro. Por ejemplo, a menudo dependemos de las estadísticas del equipo que implementa una función (en este caso, el equipo de CSP) y, por supuesto, del equipo de DevRel, que diseña cómo los desarrolladores web deben abordar un tipo determinado de problema. El texto del problema suele pasar por algunos perfeccionamientos hasta que se termina.

Por lo general, el equipo de DevTools comienza con un borrador de lo que imaginan:


## Header
Content Security Policy: include all sources of your resources in content security policy header to improve the functioning of your site

## General information
Even though some sources are included in the content security policy header, some resources accessed by your site like images, stylesheets or scripts originate from sources not included in content security policy directives.

Usage of content from not included sources is restricted to strengthen the security of your entire site.

## Specific information

### VIOLATED DIRECTIVES
`img-src 'self'`

### BLOCKED URLs
https://imgur.com/JuXCo1p.jpg

## Specific information
https://web.dev/strict-csp/

Después de la iteración, llegamos a lo siguiente:

ALT_TEXT_HERE

Como puedes ver, involucrar al equipo de funciones y a DevRel hace que la descripción sea mucho más clara y precisa.

Los problemas de CSP de tu página también se pueden descubrir en la pestaña dedicada específicamente a los incumplimientos de CSP.

Cómo depurar problemas de Trusted Types

Trabajar con TT a gran escala puede ser un desafío sin las herramientas de desarrollador adecuadas.

Mejoras en la impresión de consolas

Cuando trabajamos con objetos de confianza, nos gustaría mostrar al menos la misma cantidad de información que para la contraparte no confiable. Lamentablemente, actualmente, cuando se muestra un objeto de confianza, no se muestra información sobre el objeto unido.

Esto se debe a que el valor que se muestra en la consola se obtiene de la llamada a .valueOf() en el objeto de forma predeterminada. Sin embargo, en el caso de Trusted Type, el valor que se muestra no es muy útil. En cambio, nos gustaría tener algo similar a lo que obtienes cuando llamas a .toString(). Para lograrlo, debemos modificar V8 y Blink para implementar un manejo especial de objetos de tipo de confianza.

Aunque, por razones históricas, este control personalizado se realizó en V8, ese enfoque tiene desventajas importantes. Hay muchos objetos que requieren una visualización personalizada, pero cuyo tipo es el mismo a nivel de JS. Como V8 es JS puro, no puede distinguir los conceptos que corresponden a una API web, como un tipo de confianza. Por esa razón, V8 debe pedirle ayuda a su incorporador (Blink) para distinguirlos.

Por lo tanto, mover esa parte del código a Blink o a cualquier incorporador parece una opción lógica. Además del problema expuesto, hay muchos otros beneficios:

  • Cada incorporador puede tener su propia generación de descripciones.
  • Es mucho más fácil generar la descripción a través de la API de Blink.
  • Blink tiene acceso a la definición original del objeto. Por lo tanto, si usamos .toString() para generar la descripción, no hay riesgo de que se vuelva a definir .toString().

Pausa en caso de incumplimiento (en el modo de solo informes)

Actualmente, la única forma de depurar los incumplimientos de TT es establecer puntos de interrupción en las excepciones de JS. Dado que los incumplimientos de los TT aplicados activarán una excepción, esta función puede ser útil de alguna manera. Sin embargo, en situaciones reales, necesitas un control más detallado sobre los incumplimientos de TT. En particular, nos gustaría hacer una pausa solo en los incumplimientos de TT (no en otras excepciones), hacer una pausa también en el modo informativo y distinguir entre los diferentes tipos de incumplimientos de TT.

DevTools ya admite una amplia variedad de puntos de interrupción, por lo que la arquitectura es bastante extensible. Agregar un nuevo tipo de punto de interrupción requiere cambios en el backend (Blink), CDP y el frontend. Debemos presentar un nuevo comando de CDP, llamemos setBreakOnTTViolation. El frontend usará este comando para indicarle al backend qué tipo de incumplimientos de TT debe incumplir. El backend, en particular InspectorDOMDebuggerAgent, proporcionará una "sonda", onTTViolation(), a la que se llamará cada vez que se produzca una infracción de TT. Luego, InspectorDOMDebuggerAgent verificará si ese incumplimiento debe activar un punto de interrupción y, si ese es el caso, enviará un mensaje al frontend para pausar la ejecución.

¿Qué se hizo y qué sigue?

Desde que se introdujeron los problemas que se describen aquí, la pestaña Problemas sufrió algunos cambios:

En el futuro, planeamos usar la pestaña Problemas para mostrar más problemas, lo que permitirá descargar de Console el flujo de mensajes de error ilegibles a largo plazo.

Descarga los canales de vista previa

Considera usar Chrome Canary, Dev o Beta como tu navegador de desarrollo predeterminado. Estos canales de versión preliminar te brindan acceso a las funciones más recientes de DevTools, te permiten probar las APIs de plataformas web de vanguardia y te ayudan a encontrar problemas en tu sitio antes que tus usuarios.

Comunícate con el equipo de Chrome DevTools

Usa las siguientes opciones para hablar sobre las funciones nuevas, las actualizaciones o cualquier otro tema relacionado con DevTools.