Dez headers decidem quanto um bug vai te custar.
Digite uma URL e lemos os headers de resposta que seu servidor realmente envia. Cada um abaixo é uma única linha de configuração, e cada um transforma uma classe inteira de ataque de possível em impossível.
O que isso verifica, e como é uma boa resposta
Content-Security-Policy
O que mais importa, e o que quase nenhum app gerado por IA entrega. Ele diz ao navegador de onde o JavaScript pode vir. Sem ele, qualquer lugar em que seu app renderiza texto que outra pessoa forneceu, um nome de exibição, um comentário, a descrição de um produto vinda de uma API, vira um lugar onde código pode rodar com acesso total à sessão. Uma boa resposta começa em default-src 'self' e abre de propósito. Uma resposta ruim está ausente, ou é tão ampla que permite tudo que ela foi adicionada para impedir.
unsafe-eval, curingas e unsafe-inline dentro dessa política
Uma política pode existir e ainda assim ser decoração. 'unsafe-eval' deixa a página transformar strings em código executável, que é o mecanismo em que a maior parte das injeções se apoia. Um * solto ou um https: nas origens de script significa que o código pode carregar de qualquer host da internet. 'unsafe-inline' significa que markup injetado executa. A nuance que vale saber: 'strict-dynamic' faz os navegadores ignorarem origens de host, e um nonce ou hash faz eles ignorarem 'unsafe-inline', então uma política que carrega os dois está correta, não contraditória. Aplicamos essa regra em vez de procurar padrões no texto.
X-Frame-Options e frame-ancestors
Se alguém consegue carregar seu app dentro de um frame invisível na própria página, cobrir com os próprios botões e coletar os cliques. Não custa nada para o atacante e funciona em qualquer página onde um clique faz algo relevante: confirmar um pagamento, mudar uma permissão, apagar uma conta. Qualquer um dos dois headers fecha isso. frame-ancestors é o moderno e consegue nomear origens permitidas; X-Frame-Options: DENY é a versão bruta que navegadores antigos ainda respeitam.
X-Content-Type-Options
Um valor só, nosniff, e ele impede o navegador de adivinhar o tipo de um arquivo quando o tipo declarado parece errado. Esse chute é como um arquivo enviado que seu servidor chama de documento de texto puro acaba executado como script. Se o seu app aceita upload e devolve esses arquivos do seu próprio domínio, essa linha não é opcional.
Referrer-Policy
Quanto da URL atual viaja junto em cada clique para fora e em cada requisição a terceiros. O padrão em várias stacks envia o caminho completo, o que vaza tudo que você coloca numa URL: tokens de redefinição de senha, links de convite, ids de documento, termos de busca. strict-origin-when-cross-origin mantém a parte útil e descarta o resto.
Permissions-Policy
Quais recursos do navegador a página e o que ela embute podem pedir: câmera, microfone, geolocalização, pagamento. Negar o que você não usa significa que um script de terceiro comprometido não consegue ligar nenhum deles no silêncio. A maioria dos apps não precisa de nenhum, o que faz dessa uma das linhas mais baratas do arquivo.
Divulgação da versão do servidor
Se suas respostas anunciam a versão exata do que está rodando. nginx sozinho não diz a um atacante nada que ele não pudesse adivinhar. nginx/1.18.0 diz precisamente quais avisos de segurança ele deve ir ler. Isso não é uma vulnerabilidade sozinha, é o passo antes de uma.
Access-Control-Allow-Origin
Quais outros sites podem ler suas respostas. Um curinga aqui em geral é alguém resolvendo um erro de CORS às três da manhã, e significa que qualquer página da internet chama sua API e lê o que volta. Vale conferir a cada deploy, porque essa é a configuração com mais chance de ter sido aberta às pressas e nunca mais fechada.
Como corrigir
Todos esses são definidos num lugar só, e na maioria dos hosts esse lugar é um único arquivo de configuração. A política abaixo é um ponto de partida rígido o bastante para valer a pena e frouxo o bastante para um app típico subir: comece por aqui, olhe o console do navegador e abra só onde algo de que você realmente precisa estiver sendo bloqueado.
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Content-Security-Policy", "value": "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}# public/_headers
/*
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()// next.config.js
module.exports = {
async headers() {
return [
{
source: "/:path*",
headers: [
{ key: "Content-Security-Policy", value: "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
],
},
];
},
};export default {
async fetch(request, env) {
const response = await env.ASSETS.fetch(request);
const headers = new Headers(response.headers);
headers.set("content-security-policy", "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'");
headers.set("x-frame-options", "DENY");
headers.set("x-content-type-options", "nosniff");
headers.set("referrer-policy", "strict-origin-when-cross-origin");
headers.set("permissions-policy", "camera=(), microphone=(), geolocation=()");
return new Response(response.body, { status: response.status, headers });
},
};import helmet from "helmet";
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"],
},
},
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
}),
);Headers são o trabalho de segurança mais barato que existe, e é exatamente por isso que corrigi-los prova tão pouco. Uma pontuação perfeita aqui diz que suas respostas estão bem configuradas. Não diz nada sobre a consulta por trás do seu painel conferir quem está perguntando, sobre uma dependência instalada na primavera passada ter um furo conhecido, ou sobre uma chave de API estar parada no seu histórico do git num commit de seis meses atrás. Essas coisas vivem no código, e nenhum header compensa por elas.