Sua chave de API está no F12 — e nem toda chave ali é um problema
Abrir o F12, procurar por key e encontrar alguma coisa é rotina. O que decide
se você tem um problema não é a chave estar visível — é qual chave está.
Duas famílias de chave
Publicável. Existe para viajar no navegador. A anon key do Supabase, a
pk_live_ do Stripe, a chave do Google Maps, a chave anônima do Firebase. Elas
identificam o projeto, não autorizam operação. Quem protege não é o segredo — é
outra coisa: RLS no Supabase, restrição por domínio no Maps, regras de
segurança no Firebase.
Secreta. Autoriza operação em nome da sua conta. sk_live_ do Stripe,
service_role do Supabase, chave da OpenAI, token do Resend ou do SendGrid,
chave da AWS. No navegador, elas dão a quem encontrar exatamente o poder que
dariam ao seu servidor.
O prefixo costuma dizer qual é qual: pk_ de publishable, sk_ de secret.
service_role diz o que faz. sk-proj- da OpenAI também.
Encontrar as suas
# baixa os scripts da página e procura os padrões conhecidos
curl -s https://seusite.com.br | grep -oE 'src="[^"]+\.js"' | cut -d'"' -f2 | \
while read -r js; do
curl -s "https://seusite.com.br/${js#/}" | grep -oE \
'sk_live_[a-zA-Z0-9]+|sk-proj-[a-zA-Z0-9_-]+|AKIA[0-9A-Z]{16}|re_[a-zA-Z0-9_]+|SG\.[a-zA-Z0-9_.-]+'
done
No navegador dá para fazer o mesmo à mão: F12 → Sources → Ctrl+Shift+F →
procure por sk_, secret, service_role, AKIA.
E não esqueça do sourcemap. Se o build publicou os .js.map, o seu código
original está na web, com nomes de variável e comentários:
curl -s -o /dev/null -w "%{http_code}\n" https://seusite.com.br/_next/static/chunks/main.js.map
200 aí significa que ler o seu front ficou tão fácil quanto ler o repositório.
Como a chave secreta chega no navegador
Quase sempre por um destes três caminhos:
O prefixo público. NEXT_PUBLIC_OPENAI_API_KEY no Next, VITE_STRIPE_SECRET
no Vite. O prefixo é uma instrução de build: "embuta isto no bundle". Quem
não sabe disso usa o prefixo para "resolver" o valor que chegava undefined no
componente — e o undefined some, junto com a proteção.
A chamada direta do navegador. O app chama a API da OpenAI direto do componente React. Para isso a chave precisa estar no navegador. Não há configuração que conserte: é a arquitetura da chamada.
O assistente escolheu o caminho curto. Pedir "integra a OpenAI nesse componente" a um gerador de código costuma produzir exatamente a chamada direta, porque é a que funciona em uma tentativa. Um proxy no servidor exige criar uma rota, e ele não faz isso sem você pedir.
O conserto: a chamada muda de lugar
Não existe forma segura de manter uma chave secreta no navegador. Ela precisa sair de lá — e a chamada vai junto.
// app/api/resumo/route.ts — roda no servidor
export async function POST(req: Request) {
const { texto } = await req.json()
const r = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
// sem NEXT_PUBLIC_: fica no servidor
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: texto }],
}),
})
return Response.json(await r.json())
}
O componente passa a chamar /api/resumo. A chave nunca sai da máquina.
Duas coisas que essa rota ainda precisa, e que a versão direta não tinha como ter: exigir que o usuário esteja logado, e limitar quantas chamadas cada um faz. Sem isso você trocou "qualquer um usa a minha chave" por "qualquer um usa a minha rota" — a fatura chega igual no fim do mês.
Se a chave secreta já esteve no ar
Rotacione primeiro. No painel do provedor, gere uma nova e invalide a antiga. Só depois mexa no código — enquanto a chave antiga for válida, o conserto no repositório não protege nada, porque a que vazou continua funcionando.
Depois disso: procure no histórico do Git (ela costuma ter sido commitada antes
de ir para o .env), desligue o sourcemap em produção, e confira a fatura e os
logs de uso do serviço no período em que a chave esteve exposta.
O resumo
- Chave no F12 não é diagnóstico.
pk_eanonpodem ficar;sk_,service_roleeAKIAnão. NEXT_PUBLIC_eVITE_significam "publique isto". Não são um jeito de fazer a variável funcionar.- Chave secreta no navegador não tem conserto por configuração — a chamada precisa mudar de lugar.
- E a rota nova precisa de autenticação e limite, senão o problema só trocou de porta.
