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

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

Esta postagem do blog aborda 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 Issues recém-lançada (links em inglês).

O trabalho de implementação foi realizado em dois estágios: 1: Durante a primeira, criamos a estrutura geral de denúncias e desenvolvemos mensagens para três violações da CSP. 2. Durante o segundo, adicionamos problemas de tipos confiáveis e 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 de scripts em vários locais (XSS). Para uma introdução detalhada à CSP, acesse este link.

Uma CSP particularmente nova é a política de Tipos confiáveis(TT, na sigla em inglês), que permite uma análise dinâmica que pode prevenir sistematicamente uma grande classe de ataques de injeção em sites. Para conseguir isso, o TT suporta um site no policiamento de seu código JavaScript para permitir que apenas certos tipos de coisas sejam atribuídos a coletores 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 aplicado: em que toda 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.

Como implementar os 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 este processo mais ou menos:

  1. Como definir histórias de usuário. Identifique um conjunto de histórias de usuários no front-end do DevTools que cubra como um desenvolvedor Web precisa investigar o problema.
  2. Implementação de front-end. Com base nas histórias de usuários, identifique quais informações são necessárias para a investigação do 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 onde o problema pode ser detectado no Chrome e prepare o local para informar um problema, incluindo as informações relevantes da etapa (2).
  4. Salve e mostre os problemas. Armazene os problemas em um local apropriado e disponibilize-os para o DevTools depois de abertos.
  5. Como criar o texto dos problemas. Criar um texto explicativo que ajude o desenvolvedor Web a entender e, o mais importante, corrigir o problema

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

Antes de iniciar nosso trabalho de implementação, criamos um documento de design com histórias de usuários 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, eu quero:- - ...descobrir se o CSP é um motivo para iframes / imagens bloqueados no meu site Saiba qual diretiva da CSP causa o bloqueio de um determinado recurso - ...saber como alterar a CSP do meu site para permitir a exibição de recursos atualmente bloqueados / a execução de js bloqueados no momento.


Para explorar essa história de usuário, criamos algumas páginas da Web de exemplo simples que exibem as violações da CSP nas quais estávamos interessados e exploramos as páginas de exemplo para nos familiarizarmos com o processo. Veja algumas páginas da Web de exemplo. Abra a demonstração com a guia Problemas:

Com esse processo, aprendemos que o local da 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 no caso de um recurso ser bloqueado, e que um link direto para o elemento HTML no painel Elements 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 pelo protocolo Chrome DevTools (CDP):

Confira abaixo um trecho do documento 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 basicamente codifica uma estrutura de dados JSON. Ele é escrito em uma linguagem simples chamada PDL (linguagem de dados de protocolo). O PDL é usado 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 do 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++ a partir da definição que lida com a geração e o envio dessas estruturas de dados do back-end do Chromium C++ para o front-end do DevTools. Com essa biblioteca, um objeto ContentSecurityPolicyIssueDetails pode ser criado usando esta parte 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 definirmos quais informações queríamos disponibilizar, precisávamos explorar onde obter essas informações do Chromium.

Etapa 3: detecção de problemas

Para disponibilizar as informações para o protocolo do Chrome DevTools (CDP) no formato descrito na última seção, precisávamos encontrar o local onde as informações estavam realmente disponíveis no back-end. Felizmente, o código da CSP já tinha um gargalo usado no modo somente relatório, em que poderíamos vincular: o ContentSecurityPolicy::ReportViolation informa problemas a um endpoint de relatório (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 foram necessárias 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 relatar problemas que ocorreram antes de o DevTools ser aberto, da mesma forma que as mensagens do console são tratadas. Isso significa que não informamos problemas imediatamente para o front-end, mas usamos um armazenamento preenchido com problemas independentemente de o DevTools estar aberto ou não. Depois que o DevTools for aberto (ou qualquer outro cliente do CDP anexado), todos os problemas registrados anteriormente poderão ser repetidos 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: projetar o texto dos problemas

Elaborar o texto dos problemas é um processo que envolve várias equipes além da nossa. Por exemplo, muitas vezes contamos com insights da equipe que implementa um recurso (neste caso, seria a equipe de CSP) e, claro, da equipe de DevRel, que projeta como os desenvolvedores Web devem lidar com um determinado tipo de problema. Normalmente, o texto do problema passa por refinamento até ser concluído.

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 de recursos e DevRel torna a descrição muito mais clara e precisa!

Os problemas de CSP na sua página também podem ser descobertos 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 certas para desenvolvedores.

Impressão aprimorada no console

Quando trabalhamos com objetos confiáveis, gostaríamos de exibir pelo menos a mesma quantidade de informações que as da versão não confiável. Infelizmente, no momento, ao exibir um objeto confiável, nenhuma informação sobre o objeto encapsulado é exibida.

O motivo é que o valor mostrado no console é retirado da chamada por padrão ao .valueOf() no objeto. 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 com 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 Blink ou qualquer embedder 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 com a API Blink
  • O Blink tem acesso à definição original do objeto. Assim, se usarmos .toString() para gerar a descrição, não haverá risco de que .toString() seja redefinido.

Interrupção em caso de violação (no modo somente denúncia)

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 situações reais, você precisa de um controle mais preciso sobre as violações de TT. Em particular, queremos detalhar apenas as violações de TT (não outras exceções), fazer a divisão no modo somente relatório e distinguir entre os diferentes tipos de violações de TT.

O DevTools já oferece suporte a uma ampla variedade de pontos de interrupção, portanto a arquitetura é bastante extensível. Adicionar um novo tipo de ponto de interrupção requer alterações no back-end (Blink), no CDP e no front-end. Vamos introduzir um novo comando de CDP, que será chamado de setBreakOnTTViolation. Esse comando será usado pelo front-end para informar ao back-end sobre quais tipos de violações de TT ela deve violar. O back-end, especificamente InspectorDOMDebuggerAgent, vai fornecer uma "sondagem", onTTViolation() que será chamada sempre que ocorrer uma violação de TT. Em seguida, o InspectorDOMDebuggerAgent vai verificar se essa violação precisa acionar um ponto de interrupção. Nesse caso, ele vai enviar uma mensagem ao front-end para pausar a execução.

O que acontece e o que vem a seguir?

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

No futuro, planejamos usar a guia Issues para identificar mais problemas, o que vai possibilitar o descarregamento do console do fluxo de mensagens de erro ilegíveis a longo prazo.

Fazer o download dos 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, testam APIs modernas da plataforma Web e encontram problemas no seu site antes que os usuários o façam!

Entrar em contato com a equipe do Chrome DevTools

Use as opções a seguir para discutir os novos recursos e mudanças na postagem ou qualquer outro assunto relacionado ao DevTools.

  • Envie uma sugestão ou feedback pelo site crbug.com.
  • Informe um problema do DevTools usando Mais opções   Mais > Ajuda > Relate problemas no DevTools no DevTools.
  • Envie um tweet em @ChromeDevTools.
  • Deixe comentários nos vídeos do YouTube sobre as novidades do DevTools ou nos vídeos do YouTube com dicas sobre o DevTools.