# CSP e Trusted Types na página de verificação: fechando o caminho do XSS

<https://unifokal.com/blog/csp-e-trusted-types-em-verificacao>

Guia · UNIFOKAL · 3 de setembro de 2026 · 8 min de leitura · Produto e integração

Um script injetado na tela que abre a câmera rouba imagem de documento, não senha. Como nonce, frame-ancestors, connect-src e Trusted Types fecham o caminho, e o que quebra no processo.

A página que pede a foto do documento e liga a câmera do usuário é uma das telas mais valiosas de qualquer produto digital. Um script injetado ali não rouba uma senha que se troca em trinta segundos: rouba a imagem de um documento de identidade, a selfie da pessoa e o resultado da verificação, material que não se revoga nem se redefine. É um alvo caro, e merece uma defesa proporcional.

A defesa mais eficaz não é encontrar o último ponto de injeção do código, tarefa que nunca termina. É reduzir o que um script injetado consegue fazer, e depois eliminar a categoria de bug que permite que uma string vire script. As duas camadas têm especificação pública no W3C: Content Security Policy e Trusted Types.

Este texto é sobre montar essas duas camadas numa tela de verificação sem quebrá-la no caminho, e sobre o detalhe que derruba integrações inteiras: a política de quem hospeda a página precisa autorizar o componente de terceiro que roda dentro dela.

## O que a CSP faz, e o que ela não faz

A [Content Security Policy Level 3](https://www.w3.org/TR/CSP3/) é uma lista de permissões que a página declara em um cabeçalho HTTP, e que o navegador passa a impor. Ela responde a perguntas do tipo: de onde este documento pode carregar script, para onde ele pode abrir conexões, quais imagens pode exibir, quem pode embuti-lo num quadro.

O que ela não faz merece a mesma clareza. A CSP não corrige a falha que permitiu a injeção. Ela é contenção: mesmo que um atacante consiga colocar conteúdo na página, a política limita o estrago, tipicamente impedindo que o script execute ou que o dado colhido saia para um servidor estranho. Tratar CSP como substituto de codificação de saída e validação de entrada é o mal-entendido mais comum, e a documentação da OWASP no Cross Site Scripting Prevention Cheat Sheet é explícita ao colocá-la como camada adicional, não como remédio.

E há um detalhe que decide se a política vale alguma coisa: a diretiva de script com 'unsafe-inline'. Sozinha, sem nonce nem hash na mesma diretiva, ela autoriza qualquer script embutido no documento, que é exatamente o que o atacante injeta: a política continua bonita no cabeçalho e vazia na prática. Vale saber a exceção, porque ela é a configuração recomendada para compatibilidade: quando a mesma diretiva traz um nonce ou um hash, a especificação manda o navegador IGNORAR o 'unsafe-inline', que passa a servir só para navegadores antigos demais para entender nonce.

### Nonce por requisição, e o cache que estraga tudo

A alternativa ao 'unsafe-inline' descrita na especificação é o nonce: um valor aleatório gerado a cada resposta, colocado no cabeçalho e repetido no atributo de cada script legítimo. O navegador executa apenas os scripts cujo atributo bate com o nonce daquela resposta. Como o atacante não conhece o valor, o script injetado não roda.

A palavra decisiva é "a cada resposta". Um nonce fixo, reaproveitado entre usuários, é apenas uma senha pública: basta ler o código-fonte da página uma vez para saber o valor que autoriza qualquer script. Isso tem uma consequência arquitetural que pega muita gente de surpresa: a página não pode ser servida de um cache estático com o HTML já pronto e o nonce dentro. Ou o HTML é gerado a cada requisição, ou o nonce é injetado na borda no momento da entrega. Quem tenta conciliar cache agressivo e nonce por requisição sem esse cuidado acaba fixando o valor sem perceber, e a política vira enfeite.

Para conteúdo realmente estático existe alternativa, o hash do script, que autoriza um conteúdo exato em vez de um valor por requisição.

## As diretivas que importam nesta tela

Uma tela de verificação tem um punhado de diretivas que fazem trabalho de verdade.

- **frame-ancestors** decide quem pode embutir a sua página num quadro. É o que impede que um site hostil coloque a verificação real dentro de uma moldura enganosa, técnica próxima da que sustenta a [página de verificação falsa](https://unifokal.com/blog/pagina-de-verificacao-falsa). Numa integração legítima, essa lista contém os domínios dos clientes que embutem o fluxo, e nada além disso.
- **connect-src** limita para onde o JavaScript da página pode abrir requisições e conexões persistentes. É a diretiva que transforma um roubo de imagem em tentativa bloqueada, porque o script injetado até pode ler o que quiser da página, mas não consegue enviar para fora da lista.
- **img-src** e **media-src** limitam a origem do que a página exibe, o que também restringe truques de exfiltração por meio de URL de imagem.
- **default-src** fecha o resto, e a prática recomendada é começar por 'none' e abrir apenas o necessário, em vez do caminho inverso.
- **form-action** limita para onde um formulário pode ser submetido, detalhe esquecido em páginas que ainda têm formulários clássicos.

Diretivas apertadas também disciplinam a própria equipe: um serviço novo de métrica ou de chat passa a exigir decisão consciente, registrada na política, em vez de entrar de carona numa tag colada às pressas.

## Ligar sem quebrar: report-only e o endpoint de relatório

Publicar uma política restritiva direto em produção costuma resultar em tela branca. O caminho descrito na especificação é o modo somente relatório, no qual o navegador não bloqueia nada, apenas relata o que teria bloqueado. Roda-se a política nesse modo por algumas semanas, com tráfego real e a variedade de aparelhos que só a produção tem, e a lista de violações vira o inventário do que a página realmente usa.

O endpoint que recebe esses relatórios continua útil depois que a política entra em vigor. Ele avisa quando alguém introduziu uma dependência nova sem atualizar a política, e mostra tentativas de injeção que a política barrou, sinal de ataque que nenhum outro log da aplicação registraria. Vale filtrar o ruído: extensões de navegador geram violações em volume, e um endpoint que recebe tudo sem agregação vira lixo em vez de sinal.

## Trusted Types: atacar a causa, não o sintoma

A CSP contém o estrago. A especificação [Trusted Types](https://www.w3.org/TR/trusted-types/) do W3C ataca a causa do XSS baseado em DOM, aquele em que nenhum servidor devolve HTML malicioso: o próprio JavaScript da página pega um valor controlado pelo atacante, vindo da URL ou de uma mensagem entre janelas, e o entrega a uma função que transforma texto em marcação viva.

Com a diretiva require-trusted-types-for 'script' ativa, o navegador passa a recusar strings comuns nesses pontos perigosos. Só um objeto produzido por uma política nomeada, criada explicitamente pelo código da aplicação, é aceito. O efeito prático é que os lugares onde texto vira código deixam de estar espalhados por todo o código e passam a caber numa função auditável. Deixa de ser preciso confiar em todo desenvolvedor que escrever uma linha nova: basta revisar as políticas declaradas.

O custo é real e vale dizer sem maquiagem. Bibliotecas antigas escrevem HTML de forma direta e quebram assim que a diretiva entra. Adotar Trusted Types costuma exigir substituir dependências ou registrar políticas específicas para elas, e por isso a ordem usual é ligar em modo relatório primeiro, medir o problema e só então impor. A documentação do MDN Web Docs cobre o suporte por navegador, que não é universal, o que reforça o ponto: Trusted Types elimina uma classe de bug em quem tem suporte, e não dispensa a CSP em quem não tem.

## O ponto que o integrador esquece

Existe um detalhe que transforma uma boa política em chamado de suporte. Quando a verificação roda embutida no site do cliente, quem manda ali é a política do cliente, não a sua. Se a CSP dele não autorizar a origem do componente, a verificação simplesmente não carrega, e o sintoma que o usuário relata é uma área em branco no meio do cadastro.

Por isso a documentação de integração precisa dizer, em letras grandes, quais origens o site que hospeda deve permitir em script-src, connect-src, img-src e frame-src, e essa lista é parte do contrato: mudar de domínio de entrega sem avisar quebra todos os clientes que fizeram a lição de casa. O efeito pesa na escolha entre [widget embutido e integração por API](https://unifokal.com/blog/widget-ou-api-de-kyc), já que cada caminho tem superfície de política diferente. E a mesma disciplina vale para o envio dos arquivos: connect-src restrito só combina com [upload direto ao armazenamento](https://unifokal.com/blog/upload-seguro-de-midia) se a origem do armazenamento estiver declarada dos dois lados.

## Fontes citadas

- W3C, [Content Security Policy Level 3](https://www.w3.org/TR/CSP3/).
- W3C, [Trusted Types](https://www.w3.org/TR/trusted-types/).
- MDN Web Docs, documentação de CSP e Trusted Types, citada por nome no texto.
- OWASP, Cross Site Scripting Prevention Cheat Sheet, citado por nome no texto.
