CSP com nonce em Next.js: fechar a porta do XSS sem quebrar o Fast Refresh
Uma Content Security Policy forte com nonce por requisição neutraliza XSS mesmo quando algo escapa da sanitização. O detalhe que trava muita gente: fazê-la conviver com o hot reload em desenvolvimento.
Uma Content Security Policy (CSP) define, para o navegador, de onde scripts, estilos e imagens podem vir. Bem montada, ela é a diferença entre um XSS refletido virar incidente ou virar nota de rodapé: mesmo que um input malicioso escape da sanitização, o navegador se recusa a executar o script que não carrega a assinatura certa.
A assinatura, aqui, é um nonce — um valor aleatório gerado a cada resposta. Só o script que traz aquele nonce roda. Como o valor muda a cada requisição, um atacante não tem como prever qual usar.
Por que strict-dynamic muda o jogo
A diretiva strict-dynamic diz aos navegadores modernos para confiarem apenas no nonce — e ignorarem a lista de domínios permitidos. Um script confiável passa essa confiança adiante para os scripts que ele mesmo injeta. Isso simplifica a política e ainda a fortalece:
- Você para de manter uma lista de hosts que envelhece mal.
- Scripts de terceiros só entram se forem carregados por um script que já é confiável.
- Navegadores antigos ignoram o strict-dynamic e caem no fallback da lista de hosts.
O detalhe que quebra em desenvolvimento
O Fast Refresh do Next usa eval para trocar componentes sem recarregar a página. Uma CSP de produção — sem 'unsafe-eval' — derruba isso, e o dev server sobe com o hot reload quebrado e um EvalError no console.
A saída é liberar eval apenas em desenvolvimento, nunca em produção:
const isDev = process.env.NODE_ENV !== 'production'
const devEval = isDev ? `'unsafe-eval'` : ''
const csp = [
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${devEval}`,
].join('; ')O custo real dessa escolha aparece no cache: como o nonce muda a cada resposta, o HTML precisa ser renderizado por requisição e sai com no-store. Uma CSP mais forte custa um pouco de TTFB. Para a maioria dos sites que lidam com dados de quem visita, é uma troca que vale.
Mais sobre a diretiva na documentação do MDN sobre CSP.