Juliana Assalti
Voltar para o blog
1 min de leitura

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.