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 infracciones del CSP. 2. Durante la segunda, agregamos problemas de Trusted Type junto con algunas funciones especializadas de Herramientas para desarrolladores para depurar Trusted Types.

¿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 secuencias de comandos intercaladas o eval, lo que reduce la superficie de ataque de los ataques de secuencias de comandos entre sitios (XSS). Para obtener una introducción detallada a CSP, consulta aquí.

Una CSP particularmente nueva es la política Trusted Types(TT), que permite un análisis dinámico que puede prevenir sistemáticamente 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 de solo informes: Informa el mensaje de error como una advertencia, pero no provoca 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 se consideran problemas nuevos, el equipo de Herramientas para desarrolladores sigue aproximadamente este proceso:

  1. Definir historias de usuario Identifica un conjunto de historias de usuario en el frontend de Herramientas para desarrolladores que explique cómo un desarrollador web tendría que investigar el problema.
  2. Implementación del frontend: En función de las historias de los usuarios, identifique qué datos se necesitan para investigar el problema en el frontend (por ejemplo, una solicitud relacionada, el nombre de una cookie, una línea en una secuencia de comandos o un archivo html, etc.).
  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 bloquee un recurso. También nos pareció útil un vínculo directo al elemento HTML en el panel Elements de Herramientas para desarrolladores.

Paso 2: Implementación del frontend

Convertimos esta información en el primer borrador de la información que queríamos poner a disposición para Herramientas para desarrolladores 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 básicamente codifica una estructura de datos JSON. Está escrito en un lenguaje simple llamado PDL (lenguaje de datos de protocolo). El PDL se usa para dos propósitos. 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 que se describe 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 de solo 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 problemas de inmediato al frontend, sino que usamos un almacenamiento que contiene problemas independientemente de si Herramientas para desarrolladores 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

Diseñar el texto de los problemas es un proceso que involucra a varios equipos además del nuestro, por ejemplo, a menudo nos basamos en información del equipo que implementa una función (en este caso, sería el equipo del CSP) y, por supuesto, el equipo de DevRel, que diseña cómo se supone que los desarrolladores web deben lidiar con un cierto tipo de problema. Por lo general, el texto del problema se perfecciona hasta finalizar.

Por lo general, el equipo de Herramientas para desarrolladores comenzará 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.

Depura problemas de Trusted Types

Trabajar con TT a gran escala puede ser un desafío si no se tienen 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, en este momento, 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 ese motivo, V8 debe pedirle ayuda a su embedder (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 redefine .toString().

Interrupción 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 desglosar solo las infracciones de VC (no otras excepciones), realizar también desgloses en el modo de solo informes y distinguir entre los diferentes tipos de infracciones de TT.

Las Herramientas para desarrolladores ya admiten 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 presentaron los problemas que se describen aquí, la pestaña Problemas experimentó 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.