Validador de CSP

A maioria das políticas permite mais do que o autor imagina.

Cole uma Content-Security-Policy e veja o que um navegador de fato vai deixar passar. Nada é enviado para nós: a política é interpretada no seu navegador, então dá para checar um header que você ainda nem publicou.

Roda no seu navegador. Nada é enviado para nós.

O que isso verifica, e como é uma boa resposta

Qual diretiva realmente governa seus scripts

Navegadores usam script-src se ela existir e recorrem a default-src se não existir. Esse fallback é fonte comum de surpresa: uma política com um default-src rígido e um script-src permissivo é exatamente tão permissiva quanto o segundo, e a linha rígida acima não faz nada pelos scripts. Mostramos qual diretiva lemos, para o veredito ser conferível em vez de algo que você tem que aceitar na fé.

unsafe-eval

Permite transformar strings em código executável, que é do que a maioria dos payloads de injeção depende. Costuma estar ali porque um ajuste de bundler ou uma biblioteca de template pediu, e costuma ser removível mudando esse ajuste. Enquanto estiver ali, boa parte do resto da política é decoração.

Curingas, e quando eles não contam

Um * solto, ou uma origem de esquema como https:, permite script de qualquer host da internet. Mas um curinga de host ao lado de 'strict-dynamic' não é um furo, porque essa palavra-chave manda o navegador ignorar origens de host por completo e confiar só no que um script aprovado por nonce carregar. Verificamos tokens inteiros em vez de procurar no texto, então https://cdn.exemplo.com/* é corretamente lido como um host específico e não como um curinga só porque contém um asterisco.

unsafe-inline, e quando ele já é ignorado

O motivo mais comum de uma política aparentemente rígida não se sustentar. Ela permite scripts inline, então markup injetado executa. A nuance que derruba a maioria dos verificadores: se a mesma diretiva carrega um nonce ou um hash, os navegadores ignoram 'unsafe-inline' por completo. As políticas são escritas assim de propósito para que navegadores antigos, que não entendem nonce, ainda carreguem a página. Sinalizar esse formato seria um falso positivo na política rígida correta mais comum que existe, então reportamos como tudo certo e dizemos por quê.

base-uri, a que quase sempre falta

base-uri não é coberta por default-src, então uma política pode parecer completa e ainda assim omiti-la. Sem ela, uma tag <base> injetada reaponta todo script e link relativo da página para outro host, o que transforma uma pequena injeção de HTML em execução completa de script e passa por cima de um script-src no qual você trabalhou bastante. Custa uma diretiva.

object-src e frame-ancestors

object-src 'none' fecha conteúdo de plugin, de que quase nenhuma aplicação precisa e que de outra forma pode rodar um documento na sua origem. frame-ancestors decide quem pode embutir sua página; é a substituta moderna de X-Frame-Options e a única das duas capaz de expressar uma lista de origens permitidas.

Como corrigir

Uma política rígida é mais fácil de alcançar do que a fama sugere, desde que você a publique primeiro em modo report-only e leia o que quebra. A abordagem de nonce abaixo é a que este site usa, então é um padrão que rodamos em produção e não apenas algo que recomendamos.

Uma política rígida para começar
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none'

# 'strict-dynamic' lets a nonce-approved script load what it needs
# without you listing every CDN, which is what makes a strict policy
# survivable in a real app with a bundler.
Publique com segurança primeiro
# Same policy, different header name. Browsers report violations
# to the console and to report-to, and block NOTHING. Run it for a
# week, read what shows up, then rename the header.
Content-Security-Policy-Report-Only:
  default-src 'self'; script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'
Gerando o nonce (Cloudflare Worker)
// The nonce must be new on EVERY request. A nonce baked into a
// static file is not a nonce, it is a password the attacker also has.
const nonce = crypto.randomUUID().replaceAll("-", "");

const policy = [
  "default-src 'self'",
  `script-src 'nonce-${nonce}' 'strict-dynamic'`,
  "object-src 'none'",
  "base-uri 'self'",
  "frame-ancestors 'none'",
].join("; ");

// Then stamp the same nonce onto every <script> as you stream the HTML.
return new HTMLRewriter()
  .on("script", { element: (el) => el.setAttribute("nonce", nonce) })
  .transform(new Response(html, { headers: { "content-security-policy": policy } }));
Middleware do Next.js
// middleware.ts
import { NextResponse } from "next/server";

export function middleware(request: Request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const policy = `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'self'`;

  const headers = new Headers(request.headers);
  headers.set("x-nonce", nonce);

  const response = NextResponse.next({ request: { headers } });
  response.headers.set("content-security-policy", policy);
  return response;
}
O que esta ferramenta não consegue ver

Isto lê a política que você colou. Não consegue dizer se é a política que seu servidor realmente envia, e as duas discordam com mais frequência do que se imagina: um proxy, uma regra de CDN ou um segundo header no nível do framework podem sobrescrever ou duplicar, e quando duas políticas chegam o navegador aplica a interseção das duas. Também não consegue dizer se a política quebra seu app, coisa que só uma publicação em report-only mostra. Para ver o que seu site no ar envia, rode o verificador de headers de segurança contra a URL.

Analisar o código tambémGrátis para começar. Conecte um repositório e o primeiro scan roda em cerca de dois minutos.