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.
