CSP estrita: como reduzir o risco de XSS sem transformar o deploy em um campo minado

Content Security Policy, ou CSP, não corrige uma falha de XSS. Ela diminui a chance de que uma falha já existente consiga executar script no navegador do usuário. Essa diferença parece burocrática, mas evita duas armadilhas: tratar o header como item de checklist e achar que um allowlist enorme de domínios equivale a uma política forte.

Uma CSP baseada em nonces ou hashes permite que o navegador execute apenas scripts que receberam uma prova explícita de confiança. É uma camada de defesa em profundidade. Validação de entrada, escaping por contexto e correção da vulnerabilidade continuam obrigatórios; a CSP entra para tornar a exploração mais difícil caso uma dessas barreiras falhe.

Por que listas de domínios não bastam

Uma política como script-src 'self' https://cdn.exemplo.com parece restritiva, até o projeto adicionar analytics, tag manager, widget de suporte, mapas e mais cinco integrações. A lista cresce, os scripts permitidos carregam outros scripts e a auditoria vira uma caça a URLs. A documentação do web.dev alerta que CSPs baseadas apenas em allowlists de host frequentemente podem ser contornadas.

Uma CSP estrita troca a pergunta “de qual domínio pode vir?” por “este script foi autorizado nesta resposta?”. Em páginas dinâmicas, isso geralmente significa gerar um nonce criptograficamente aleatório para cada resposta, colocá-lo no header e nos elementos script confiáveis.

Content-Security-Policy:
  script-src 'nonce-R4nd0mValue' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none'

O nonce do exemplo é só ilustrativo. No servidor ele precisa ser imprevisível e novo a cada resposta. Reutilizá-lo anula boa parte da proteção, então não coloque uma string fixa em variável de ambiente e declare vitória antes do café.

O papel de strict-dynamic

Com 'strict-dynamic', a confiança dada por nonce ou hash a um script raiz pode ser propagada para scripts que ele carregar dinamicamente. Isso é útil para bundles, loaders e integrações de terceiros que injetam dependências. Sem isso, o script raiz autorizado pode falhar ao montar o restante da aplicação.

Há uma contrapartida importante: confiar em um loader significa confiar no que ele decide carregar. Se esse código cria tags script a partir de dados controláveis por atacante, strict-dynamic amplia o estrago. A política continua exigindo que a aplicação trate URLs, HTML e entradas como dados hostis.

Adoção sem derrubar a aplicação

Comece com Content-Security-Policy-Report-Only. Nesse modo, o navegador registra violações sem bloquear recursos. É a maneira mais segura de descobrir scripts inline esquecidos, eventos onclick, eval(), fontes, imagens e chamadas de API que seu app faz fora do caminho feliz.

  • Inventarie scripts, estilos e conexões realmente necessários.
  • Remova handlers inline e mova comportamento para addEventListener().
  • Gere nonce no servidor, ou use hashes para conteúdo estático.
  • Observe os relatórios e corrija exceções com causa conhecida.
  • Só então ative o header em modo de bloqueio.

Também não esqueça de diretivas que protegem fora de script-src. object-src 'none' corta plugins legados; base-uri 'none' impede injeção de base; frame-ancestors controla quem pode embutir sua página. Cada uma resolve um pedaço de superfície que uma política focada apenas em scripts deixa exposto.

Uma CSP forte costuma revelar dívida técnica: templates com script inline, dependências que usam avaliação dinâmica e widgets externos opacos. Isso é desconfortável, mas útil. O header deixa de ser um enfeite de segurança quando vira um inventário honesto do código que recebe permissão para rodar no navegador.

Fontes