Chave de API exposta no frontend: risco e como corrigir
Tudo o que vai para o navegador é público, inclusive as variáveis de ambiente com prefixo VITE_. Qual chave pode ficar exposta, qual nunca pode, como achar no seu app e o que fazer quando já vazou.
Tudo o que vai para o navegador é público
Ferramentas de build como Vite e Next.js separam variáveis de ambiente em duas categorias: as que ficam no servidor e as que são embutidas no JavaScript enviado ao navegador. Estas últimas são marcadas por um prefixo, e qualquer visitante consegue lê-las no DevTools.
| Ferramenta | Prefixo que vai para o navegador |
|---|---|
| Vite (React, Vue, Svelte) | VITE_ |
| Next.js | NEXT_PUBLIC_ |
| Create React App | REACT_APP_ |
| Expo | EXPO_PUBLIC_ |
O prefixo não é um descuido da ferramenta: é um aviso. Ele diz "esta variável será pública". Segredo com esse prefixo é segredo publicado.
Qual chave pode ser pública e qual nunca pode
| Serviço | Pode ficar no navegador | Nunca no navegador |
|---|---|---|
| Supabase | Chave anon ou publishable, desde que toda tabela tenha RLS | service_role ou chave secreta (sb_secret_…): ignora todo o RLS |
| Firebase | Configuração web (apiKey, projectId), desde que as Security Rules estejam certas | Conta de serviço do Admin SDK (arquivo JSON) |
| Stripe | Chave publicável (pk_…) | Chave secreta (sk_…) e segredo de webhook |
| OpenAI, Anthropic e similares | Nenhuma | Qualquer chave de API: dá para gastar a sua conta |
| Google Maps | Chave restrita por domínio e por API | Chave sem restrição |
| E-mail (Resend, SendGrid) | Nenhuma | Qualquer chave: dá para enviar e-mail em seu nome |
Como achar chaves expostas no seu app
No app publicado. Abra o site, pressione F12, vá em Sources e use a busca em todos os arquivos por service_role, sk-, sk_live, secret e key. Ou baixe o pacote e procure no terminal:
curl -s https://seu-app.com/ | grep -o '/assets/[^"]*\.js' \
| xargs -I{} curl -s https://seu-app.com{} \
| grep -oE '(sk-[A-Za-z0-9_-]{20,}|sk_live_[A-Za-z0-9]{20,}|service_role)'Chaves do Supabase em formato JWT (começam com eyJ) dizem qual é o papel delas. Cole o trecho do meio, entre os dois pontos, em um decodificador de base64: se aparecer "role":"service_role", é crítico.
No repositório, inclusive no histórico. Uma chave apagada continua nos commits antigos. Ferramentas como o Gitleaks varrem todo o histórico:
gitleaks git . --verboseA chave já vazou: o que fazer, na ordem
- Revogue e gere outra. No painel do serviço, antes de qualquer coisa. Considere que alguém já copiou.
- Confira o uso. Logs, faturamento e acessos desde a data em que a chave foi publicada.
- Mova a chamada para o servidor. Uma Edge Function do Supabase, uma função serverless ou o seu backend guardam a chave nova; o navegador chama essa função, que confere o usuário antes de usar a chave.
- Impeça a volta. Coloque uma varredura de segredos no CI, que falhe o build quando encontrar uma chave.
sb_secret_…) podem ser revogadas uma a uma. Nas chaves antigas em formato JWT, trocar a service_role exige gerar um novo segredo JWT no painel, o que também invalida a chave anon e encerra as sessões dos usuários. Planeje a troca.Limpar o histórico do git (com git filter-repo) é opcional depois da revogação: a chave antiga já não funciona, e reescrever o histórico atrapalha todo o time.
Como evitar que aconteça de novo
- Segredo nunca com prefixo público (
VITE_,NEXT_PUBLIC_). - Toda chamada a serviço pago passa por uma função no servidor, que confere quem está chamando e limita o uso.
- Varredura de segredos a cada pull request (Gitleaks ou TruffleHog no CI).
- Revisão do pacote publicado antes de cada lançamento, procurando chaves e source maps.