Capítulo 34 de 456

Content Security Policy

Core Idea

Proteja a app contra XSS, clickjacking e code injection com CSP; use nonce dinâmico via Proxy quando precisa permitir scripts inline específicos, ou SRI (experimental) quando quer manter geração estática com CSP estrita.

Key Concepts

  • Nonce: string aleatória de uso único que libera execução de um script/style inline específico que carrega o valor correspondente.
  • Proxy (proxy.ts): gera o nonce por request, seta o header Content-Security-Policy e um header customizado x-nonce antes do render.
  • Dynamic rendering obrigatório com nonce: nonce só existe por request; páginas estáticas são geradas em build time sem request/response headers, então não recebem nonce. Use await connection() pra forçar dynamic rendering.
  • Extração automática do nonce: Next.js faz parse do header Content-Security-Policy, extrai via padrão 'nonce-{value}' e aplica automaticamente a scripts do framework, bundles JS da página, estilos/scripts inline gerados pelo Next.js e <Script nonce={nonce}>.
  • 'unsafe-eval' em dev: React usa eval em desenvolvimento para reconstruir stack traces do servidor no browser; não necessário em produção.
  • Subresource Integrity (SRI, experimental): alternativa a nonce baseada em hash de arquivo gerado em build time (experimental.sri.algorithm); permite manter static generation e cache em CDN, mas app-router only e não cobre scripts gerados dinamicamente.
  • PPR incompatível com nonce: Partial Prerendering não funciona com CSP baseado em nonce porque scripts do shell estático não têm acesso ao nonce.

Code Examples

import { NextRequest, NextResponse } from 'next/server'

export function proxy(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const isDev = process.env.NODE_ENV === 'development'
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ''};
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' blob: data:;
    object-src 'none';
    base-uri 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`
  const contentSecurityPolicyHeaderValue = cspHeader.replace(/\s{2,}/g, ' ').trim()
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)
  requestHeaders.set('Content-Security-Policy', contentSecurityPolicyHeaderValue)
  const response = NextResponse.next({ request: { headers: requestHeaders } })
  response.headers.set('Content-Security-Policy', contentSecurityPolicyHeaderValue)
  return response
}
  • O que demonstra: geração de nonce por request e propagação via header duplo (request + response).
import { headers } from 'next/headers'
import Script from 'next/script'

export default async function Page() {
  const nonce = (await headers()).get('x-nonce')
  return <Script src="https://www.googletagmanager.com/gtag/js" strategy="afterInteractive" nonce={nonce} />
}
  • O que demonstra: leitura do nonce em Server Component pra aplicar em script de terceiro.
module.exports = {
  experimental: { sri: { algorithm: 'sha256' } },
  async headers() {
    return [{ source: '/(.*)', headers: [{ key: 'Content-Security-Policy', value: cspHeader.replace(/\n/g, '') }] }]
  },
}
  • O que demonstra: CSP sem nonce (static generation preservada) combinado com SRI hash-based.

Reference Tables

AbordagemStatic generationCDN cacheCusto
Nonce (Proxy)Não (dynamic obrigatório)Não por padrãoMaior carga de servidor, sem PPR
SRI (experimental)SimSimSó cobre scripts conhecidos em build time
VersãoMudança
v14.0.0Suporte experimental a SRI (hash-based CSP)
v13.4.20Recomendada para handling correto de nonce e parsing do header CSP

Anti-patterns

  • Nonce sem forçar dynamic rendering: build passa, mas erro em runtime; nonce exige request real.
  • CSP com nonce + PPR habilitado: incompatibilidade, shell estático não recebe nonce.
  • Esquecer de adicionar domínio de terceiro ao CSP: script bloqueado silenciosamente (ex.: GTM precisa de script-src e connect-src explícitos).

Key Takeaways

  1. Nonce exige dynamic rendering total: sem ISR, sem cache CDN por padrão, maior custo de servidor.
  2. SRI é a alternativa que preserva static generation, mas é experimental, App Router only, e não cobre scripts dinâmicos.
  3. O Proxy é o único lugar correto pra gerar o nonce por request; sem CSP não precisa de Proxy nenhum.
  4. Use matcher no Proxy ignorando prefetches (next-router-prefetch) e assets estáticos pra não gerar overhead desnecessário.
  5. Scripts de terceiro (GTM, analytics) precisam do domínio explicitamente liberado em script-src/connect-src/img-src, além do nonce.

Connects To

  • proxy.js (file convention): mecanismo que injeta o header CSP e nonce.
  • headers() function: leitura do nonce em Server Components.
  • next/script: prop nonce para scripts de terceiro sob CSP.