Como funciona esta verificação
O Scanaris volta a pedir a tua página principal com um cabeçalho Origin de um domínio inventado, como faria o navegador se outro site tentasse chamar-te, e vê o que o teu servidor responde. Se Access-Control-Allow-Origin devolver essa mesma origem e além disso Access-Control-Allow-Credentials for true, abre um achado de severidade alta: o teu servidor reflete qualquer origem com credenciais. Se responder com o wildcard * juntamente com credenciais, abre um de severidade baixa: os navegadores rejeitam essa combinação, mas denuncia uma configuração feita às cegas. Só se testa o URL analisado; se a tua API vive noutra rota ou domínio, testa-a com o comando mais abaixo.
O que um atacante faz com isto
Monta uma página em qualquer domínio com um fetch à tua API a usar credentials: 'include'. Quando um dos teus utilizadores, com sessão aberta, visita essa página, o navegador anexa os cookies dele, o teu servidor responde com os dados do utilizador e, como a política CORS o permite, a página do atacante lê-os. Perfil, encomendas, mensagens, o que a API devolver. O utilizador não vê nada: acontece em segundo plano.
Porque acontece
O CORS existe para que o navegador bloqueie por defeito as leituras entre domínios. Quando uma chamada falha em desenvolvimento (a app numa porta, a API noutra), a solução rápida que os tutoriais e as ferramentas de IA sugerem é "permitir todas as origens", e para os cookies funcionarem acrescenta-se "com credenciais". Em produção isso significa que a proteção ficou desativada. Mais detalhe em CORS mal configurado: a falha silenciosa que expõe os teus utilizadores.
Como corrigir
- Lista explícita de origens. Só os domínios que de facto chamam a tua API a partir do navegador. Em Express:
cors({ origin: ['https://app.oteudominio.com'], credentials: true }). Em Next.js, no Route Handler, compara oOrigincom a tua lista antes de o devolver. Em Nuxt, comrouteRulese a opçãocorsou um middleware de servidor. - Nunca reflitas a origem. Copiar o valor de
Originpara a resposta "porque assim funciona tudo" é exatamente o erro que o check mede. - Se não precisas de cookies entre domínios, tira o
Access-Control-Allow-Credentials. Sem credenciais, um*numa API pública só de leitura é aceitável. - Acrescenta Vary: Origin quando a resposta depender da origem, para que nenhuma cache sirva a um domínio a resposta de outro.
- Cookies com SameSite=Lax ou Strict como segunda barreira: mesmo que o CORS falhasse, o navegador não os enviaria a partir de outro site.
Confirma que está corrigido
curl -sI -H "Origin: https://evil.example" https://oteudominio.com/api/me | grep -i "access-control"
O correto é que não apareça Access-Control-Allow-Origin para essa origem, ou que apareça com o teu domínio e não com evil.example. Testa também com o teu próprio domínio no Origin para confirmar que a tua app continua a funcionar.
Perguntas frequentes
A minha API é pública e sem login. É um problema?
Não, se não houver cookies nem cabeçalhos de autorização. Access-Control-Allow-Origin: * sem credenciais é a configuração correta para uma API aberta.
Tenho vários subdomínios que chamam a API.
Lista-os todos ou valida o Origin contra um padrão estrito do teu domínio raiz. Nunca valides com "contém oteudominio.com": oteudominio.com.evil.example cumpre isso.
E o localhost para desenvolver?
Acrescenta-o só no ambiente de desenvolvimento, com uma variável de ambiente. Um http://localhost:3000 na lista de produção é uma origem que qualquer programa no computador do utilizador pode usar.