Como implementar a CSP e depurar os Tipos confiáveis no Chrome DevTools

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

Nesta postagem do blog, falamos sobre a implementação do suporte do DevTools para depurar problemas da Política de Segurança de Conteúdo (CSP) com a ajuda da guia Problemas, lançada recentemente.

O trabalho de implementação foi feito durante dois estágios: 1. Durante a primeira, criamos o framework geral de relatórios e as mensagens de problema para três problemas de violação de CSP. 2. Durante a segunda, adicionamos problemas de Tipos confiáveis com alguns recursos especializados do DevTools para depuração de Tipos confiáveis.

O que é uma Política de Segurança de Conteúdo?

A Política de Segurança de Conteúdo (CSP) permite restringir determinados comportamentos em um site para aumentar a segurança. Por exemplo, a CSP pode ser usada para proibir scripts inline ou eval, o que reduz a superfície de ataque para ataques de scripting em vários sites (XSS). Para uma introdução detalhada à CSP, leia este artigo.

Uma CSP particularmente nova é a política de Tipos confiáveis(TT), que permite uma análise dinâmica que pode impedir sistematicamente uma grande classe de ataques de injeção em sites. Para isso, os TTs ajudam um site a policiar o código JavaScript para permitir que apenas determinados tipos de coisas sejam atribuídos a coletores de DOM, como innerHTML.

Um site pode ativar uma política de segurança de conteúdo incluindo um cabeçalho HTTP específico. Por exemplo, o cabeçalho content-security-policy: require-trusted-types-for 'script'; trusted-types default ativa a política de TT para uma página.

Cada política pode operar em um destes modos:

  • modo restrito: em que cada violação da política é um erro;
  • Modo somente relatório: informa a mensagem de erro como um aviso, mas não causa uma falha na página da Web.

Implementação de problemas da Política de Segurança de Conteúdo na guia Problemas

O objetivo desse trabalho era melhorar a experiência de depuração de problemas de CSP. Ao considerar novos problemas, a equipe do DevTools segue aproximadamente este processo:

  1. Definição de histórias de usuário. Identifique um conjunto de histórias de usuário no front-end do DevTools que mostre como um desenvolvedor web precisaria investigar o problema.
  2. Implementação de front-end. Com base nas histórias de usuário, identifique quais informações são necessárias para investigar o problema no front-end (por exemplo, uma solicitação relacionada, o nome de um cookie, uma linha em um script ou arquivo HTML etc.).
  3. Detecção de problemas. Identifique os locais no navegador em que o problema pode ser detectado no Chrome e instrumente o local para informar um problema, incluindo as informações relevantes da etapa (2).
  4. Salvar e mostrar os problemas. Armazenar os problemas em um local apropriado e disponibilizá-los para o DevTools quando ele for aberto
  5. Como criar o texto dos problemas. Crie um texto explicativo que ajude o desenvolvedor web a entender e, mais importante, corrigir o problema.

Etapa 1: definir histórias de usuário para problemas de CSP

Antes de começar o trabalho de implementação, criamos um documento de design com histórias de usuário para entender melhor o que precisávamos fazer. Por exemplo, escrevemos a seguinte história de usuário:


Como desenvolvedor que acabou de perceber que parte do meu site está bloqueada, quero:- ...descobrir se a CSP é o motivo do bloqueio de iframes / imagens no meu site - ...saber qual diretiva da CSP causa o bloqueio de um determinado recurso - ...saber como mudar a CSP do meu site para permitir a exibição de recursos atualmente bloqueados / a execução de js atualmente bloqueados.


Para analisar essa história de usuário, criamos algumas páginas da Web de exemplo simples que exibiam as violações de CSP que nos interessavam e as analisamos para nos familiarizar com o processo. Confira alguns exemplos de páginas da Web (abra a demonstração com a guia Problemas aberta):

Com esse processo, descobrimos que o local de origem era a informação mais importante para depurar problemas de CSP. Também achamos útil encontrar rapidamente o iframe e a solicitação associados caso um recurso seja bloqueado, e um link direto para o elemento HTML no painel Elementos do DevTools também pode ser útil.

Etapa 2: implementação de front-end

Transformamos esse insight no primeiro rascunho das informações que queríamos disponibilizar para o DevTools usando o Chrome DevTools Protocol (CDP):

Confira abaixo o trecho 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

A definição acima codifica uma estrutura de dados JSON. Ele é escrito em uma linguagem simples chamada PDL (linguagem de dados de protocolo). A PDL é usada para duas finalidades. Primeiro, usamos o PDL para gerar as definições do TypeScript de que o front-end do DevTools depende. Por exemplo, a definição de PDL acima gera a seguinte interface 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;
}

Em segundo lugar, e provavelmente mais importante, geramos uma biblioteca C++ com base na definição que processa a geração e o envio dessas estruturas de dados do back-end do Chromium em C++ para o front-end do DevTools. Com essa biblioteca, um objeto ContentSecurityPolicyIssueDetails pode ser criado usando o seguinte trecho de 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();

Depois de decidir quais informações queríamos disponibilizar, precisávamos descobrir onde conseguir essas informações do Chromium.

Etapa 3: detecção de problemas

Para disponibilizar as informações para o Chrome DevTools Protocol (CDP) no formato descrito na última seção, precisamos encontrar o local em que as informações estavam realmente disponíveis no back-end. Felizmente, o código da CSP já tinha um gargalo usado para o modo somente relatórios, em que podíamos nos conectar: o ContentSecurityPolicy::ReportViolation informa problemas a um endpoint de relatórios (opcional) que pode ser configurado no cabeçalho HTTP da CSP. A maioria das informações que queríamos informar já estava disponível, então não foi necessário fazer grandes mudanças no back-end para que nossa instrumentação funcionasse.

Etapa 4: salvar e mostrar os problemas

Uma pequena complicação é o fato de que também queríamos informar problemas que ocorreram antes da abertura do DevTools, de forma semelhante ao tratamento de mensagens do console. Isso significa que não relatamos problemas imediatamente ao front-end, mas usamos um armazenamento que é preenchido com problemas, independentemente de o DevTools estar aberto ou não. Depois que o DevTools é aberto (ou qualquer outro cliente do CDP é anexado), todos os problemas registrados anteriormente podem ser reproduzidos do armazenamento.

Isso concluiu o trabalho de back-end, e agora precisávamos nos concentrar em como mostrar o problema no front-end.

Etapa 5: criar o texto dos problemas

A criação do texto dos problemas é um processo que envolve várias equipes além da nossa. Por exemplo, geralmente contamos com insights da equipe que implementa um recurso (neste caso, a equipe de CSP) e, claro, da equipe de DevRel, que projeta como os desenvolvedores web devem lidar com um determinado tipo de problema. O texto do problema geralmente passa por alguns refinamentos até ser finalizado.

Normalmente, a equipe do DevTools começa com um rascunho do que imagina:


## 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/

Após a iteração, chegamos a:

ALT_TEXT_HERE

Como você pode ver, envolver a equipe do recurso e o DevRel torna a descrição muito mais clara e precisa.

Os problemas de CSP na sua página também podem ser encontrados na guia dedicada especificamente a violações de CSP.

Como depurar problemas de Tipos Confiáveis

Trabalhar com TT em grande escala pode ser um desafio sem as ferramentas para desenvolvedores certas.

Impressão de console aprimorada

Ao trabalhar com objetos confiáveis, queremos mostrar pelo menos a mesma quantidade de informações que a contraparte não confiável. Infelizmente, ao exibir um objeto confiável, nenhuma informação sobre o objeto encapsulado é mostrada.

Isso ocorre porque o valor exibido no console é obtido chamando .valueOf() no objeto por padrão. No entanto, no caso do tipo confiável, o valor retornado não é muito útil. Em vez disso, queremos algo semelhante ao que você recebe ao chamar .toString(). Para isso, precisamos modificar o V8 e o Blink para introduzir um tratamento especial para objetos de tipo confiável.

Embora, por motivos históricos, esse tratamento personalizado tenha sido feito no V8, essa abordagem tem desvantagens importantes. Há muitos objetos que exigem exibição personalizada, mas que têm o mesmo tipo no nível do JS. Como o V8 é JS puro, ele não consegue distinguir conceitos que correspondem a uma API da Web, como um tipo confiável. Por isso, o V8 precisa pedir ajuda ao incorporador (Blink) para diferenciá-los.

Portanto, mover essa parte do código para o Blink ou qualquer incorporador parece uma escolha lógica. Além do problema exposto, há muitos outros benefícios:

  • Cada incorporador pode ter a própria geração de descrição
  • É muito mais fácil gerar a descrição usando a API Blink.
  • O Blink tem acesso à definição original do objeto. Portanto, se usarmos .toString() para gerar a descrição, não haverá risco de que .toString() seja redefinido.

Interrupção por violação (no modo somente relatório)

Atualmente, a única maneira de depurar violações de TT é definindo pontos de interrupção em exceções de JS. Como as violações de TT aplicadas acionam uma exceção, esse recurso pode ser útil. No entanto, em cenários reais, é necessário um controle mais refinado sobre as violações de TT. Em especial, gostaríamos de interromper apenas as violações de TT (não outras exceções), interromper também no modo somente relatório e distinguir entre os diferentes tipos de violações de TT.

O DevTools já é compatível com uma grande variedade de pontos de interrupção, então a arquitetura é bastante extensível. Para adicionar um novo tipo de ponto de interrupção, é necessário fazer mudanças no back-end (Blink), no CDP e no front-end. Vamos introduzir um novo comando do CDP, chamado setBreakOnTTViolation. Esse comando será usado pelo front-end para informar ao back-end em que tipo de violações de TT ele deve ser interrompido. O back-end, em especial InspectorDOMDebuggerAgent, vai fornecer uma "sonda", onTTViolation(), que será chamada sempre que ocorrer uma violação de TT. Em seguida, o InspectorDOMDebuggerAgent vai verificar se essa violação deve acionar um ponto de interrupção e, se for o caso, vai enviar uma mensagem ao front-end para pausar a execução.

O que foi feito e o que vem a seguir?

Desde que os problemas descritos aqui foram introduzidos, a guia Problemas passou por algumas mudanças:

No futuro, planejamos usar a guia Problemas para mostrar mais problemas, o que vai permitir que o Console seja descarregado do fluxo de mensagens de erro ilegíveis a longo prazo.

Baixar os canais de visualização

Use o Chrome Canary, Dev ou Beta como seu navegador de desenvolvimento padrão. Esses canais de pré-lançamento dão acesso aos recursos mais recentes do DevTools, permitem testar APIs de plataforma da Web de última geração e ajudam a encontrar problemas no seu site antes dos usuários.

Entre em contato com a equipe do Chrome DevTools

Use as opções a seguir para discutir os novos recursos, atualizações ou qualquer outro assunto relacionado ao DevTools.