Content Security Policy, ou CSP, é um cabeçalho HTTP que define quais fontes de scripts, estilos, imagens, fontes, frames e conexões podem ser usadas por uma página. Em aplicações Node.js, a CSP reduz o impacto de XSS e carregamento de recursos não autorizados.
Uma política eficiente não é apenas copiar default-src 'self'. É preciso mapear os recursos reais, eliminar scripts inline, usar nonce ou hash, testar em modo report-only e monitorar violações.
Política básica
import helmet from 'helmet';
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
fontSrc: ["'self'"],
connectSrc: ["'self'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
},
}));default-src funciona como fallback. Diretivas específicas substituem o fallback para aquele tipo de recurso.
Nonce para scripts
import crypto from 'node:crypto';
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(helmet.contentSecurityPolicy({
directives: {
scriptSrc: [
"'self'",
(req, res) => `'nonce-${res.locals.nonce}'`,
],
},
}));No HTML:
<script nonce="{{nonce}}" src="/assets/app.js"></script>O nonce deve ser imprevisível, exclusivo por resposta e aplicado somente a scripts confiáveis.
Hashes
Para um script inline estático, gere um hash SHA-256 e inclua:
script-src 'self' 'sha256-BASE64_DO_HASH'Qualquer mudança no conteúdo exige atualizar o hash. Hashes são úteis para trechos pequenos e imutáveis.
Evite unsafe-inline
'unsafe-inline' permite execução de scripts inline e enfraquece a proteção contra XSS. Remova handlers como onclick do HTML e registre eventos no JavaScript externo.
Evite unsafe-eval
'unsafe-eval' permite eval, new Function e padrões semelhantes. Algumas ferramentas de desenvolvimento dependem disso, mas produção deve evitar. Use builds que não exigem eval.
strict-dynamic
'strict-dynamic' permite que um script autorizado por nonce carregue outros scripts. É poderoso, mas exige entendimento de compatibilidade e cadeia de confiança.
connect-src
Controla Fetch, XMLHttpRequest, WebSocket e EventSource:
connectSrc: [
"'self'",
'https://api.example.com',
'wss://realtime.example.com',
]Inclua somente destinos necessários. Em desenvolvimento, adicione hosts locais em configuração separada.
img-src
Imagens externas, data URLs e blobs precisam ser declarados. Permitir https: aceita qualquer domínio HTTPS; prefira hosts específicos quando possível.
frame-ancestors
Controla quem pode incorporar a página em iframe e reduz clickjacking:
frameAncestors: ["'self'", 'https://portal.example.com']É mais flexível que X-Frame-Options para múltiplas origens.
frame-src e child-src
Controlam quais frames a página pode carregar. Não confunda com frame-ancestors, que controla quem pode carregar a sua página.
form-action
Limita destinos de formulários. Isso reduz exfiltração causada por HTML injetado:
formAction: ["'self'", 'https://payments.example.com']base-uri
Um elemento <base> injetado pode alterar URLs relativas. Restrinja com base-uri 'self' ou 'none'.
object-src
Plugins legados raramente são necessários. Use:
objectSrc: ["'none'"]upgrade-insecure-requests
Instrui o navegador a transformar recursos HTTP em HTTPS. Em desenvolvimento local pode causar problemas; ajuste apenas nesse ambiente.
block-all-mixed-content
Navegadores modernos já bloqueiam vários conteúdos mistos. A política reforça que recursos HTTP não devem ser carregados em páginas HTTPS.
Report-Only
Implante inicialmente sem bloquear:
app.use(helmet.contentSecurityPolicy({
reportOnly: true,
directives: policy,
}));Analise violações e corrija recursos legítimos antes de ativar enforcement.
Endpoint de relatórios
app.post('/internal/csp-report',
express.json({ type: ['application/csp-report', 'application/reports+json'], limit: '32kb' }),
(req, res) => {
logger.warn({ report: sanitizeReport(req.body) }, 'Violação CSP');
res.sendStatus(204);
},
);Relatórios são dados não confiáveis. Limite tamanho, aplique rate limiting, sanitize URLs e não registre tokens.
report-uri e report-to
report-uri é amplamente conhecido, enquanto Reporting API usa report-to ou Reporting-Endpoints. Avalie suporte dos navegadores e mantenha compatibilidade durante migração.
Templates server-side
Garanta que o nonce chega ao layout e não é inserido em conteúdo controlado pelo usuário. Um atacante que obtém o nonce por injeção no mesmo documento pode contornar a política.
SPAs
SPAs frequentemente precisam de API, analytics, fontes e CDN. Liste cada origem e evite liberar esquemas inteiros. Ferramentas de build devem produzir arquivos externos com hashes de conteúdo.
Next.js, Vite e bundlers
Frameworks podem inserir scripts inline de hidratação. Consulte a integração específica e use nonce suportado oficialmente. Não adicione unsafe-inline apenas para silenciar erros.
CDN
Ao carregar bibliotecas de CDN, fixe domínio e considere Subresource Integrity:
<script
src="https://cdn.example.com/lib.min.js"
integrity="sha384-HASH"
crossorigin="anonymous"></script>SRI verifica o conteúdo, enquanto CSP controla a origem. As duas proteções se complementam.
Inline styles
Estilos inline também podem exigir nonce ou hash. Migrar para arquivos CSS ou classes reduz a necessidade de permissões amplas.
data e blob
Permitir data: ou blob: deve ser específico por diretiva. Não adicione globalmente. Blobs podem ser necessários para workers, downloads ou previews.
Web Workers
Configure worker-src:
workerSrc: ["'self'", 'blob:']Remova blob se a aplicação não precisar.
Testes automatizados
const response = await request(app).get('/');
const csp = response.headers['content-security-policy'];
expect(csp).toContain("default-src 'self'");
expect(csp).toContain("object-src 'none'");
expect(csp).not.toContain("'unsafe-eval'");Testes end-to-end devem abrir páginas e observar console, rede, login, pagamentos e recursos externos.
Observabilidade
Agrupe violações por diretiva, origem, release e página. Um pico após deploy pode indicar recurso legítimo bloqueado; origens estranhas podem revelar extensão, injeção ou ataque.
Segurança e privacidade
Relatórios podem conter URL da página, URL bloqueada e trechos. Remova query strings e dados pessoais antes de armazenar.
Erros comuns
- usar unsafe-inline em toda a aplicação;
- permitir qualquer domínio HTTPS;
- reutilizar nonce;
- não testar report-only;
- confundir frame-src e frame-ancestors;
- registrar relatórios sem sanitização;
- ignorar WebSocket e EventSource em connect-src;
- desativar CSP para corrigir uma integração;
- não revisar a política após remover serviços.
Fluxo recomendado
- inventarie recursos;
- comece com default-src self;
- adicione diretivas mínimas;
- remova scripts inline;
- use nonce ou hash;
- ative report-only;
- corrija violações;
- ative bloqueio;
- monitore por release;
- revise periodicamente.
Combine CSP com Helmet no Node.js, CORS, TLS e HTTPS e logs com Pino.
Consulte a documentação de CSP da MDN e o guia de CSP da OWASP.


