Se seu app web chama OpenAI, Claude ou Gemini diretamente do navegador, sua chave de API fica exposta no navegador, e qualquer pessoa que abrir o DevTools pode copiá-la. Você pode confirmar isso em 2 minutos: abra a aba Network, envie uma requisição de IA e procure por um cabeçalho Authorization, x-api-key ou x-goog-api-key indo para o provedor. Se você vir sua chave ali, rotacione-a hoje e mova a chamada para trás de uma rota de servidor.
A seguir: a verificação, a ordem da correção e como seu agente de código pode testar a rota corrigida.
Por que uma chave de API exposta no navegador é um problema
Uma chave de API exposta no navegador é um segredo que vai dentro do seu código de frontend ou do tráfego de rede, onde qualquer visitante pode lê-lo. Se o navegador envia a chave, o usuário pode copiá-la e chamar o provedor na sua conta.
O guia de chaves de API do Gemini do Google diz "Não codifique chaves de API diretamente em apps web ou mobile" porque "chaves compiladas em código do lado do cliente podem ser extraídas pelos usuários", e orienta a "executar um servidor proxy de backend para fazer as chamadas de API reais". O guia de segurança de chaves de API da OpenAI traz a mesma regra: encaminhe as requisições pelo seu próprio backend em vez de enviar a chave para o cliente.
O README do openai-node diz que o suporte a navegador é desativado por padrão "para evitar expor suas credenciais secretas de API", e você precisa definir dangerouslyAllowBrowser: true para ativá-lo. O SDK TypeScript da Anthropic usa o mesmo nome de opção, e sua API só aceita chamadas de navegador (CORS) quando a requisição carrega o cabeçalho anthropic-dangerous-direct-browser-access: true, como Simon Willison documentou quando isso foi lançado.
O padrão é comum em apps construídos rapidamente com construtores de apps com IA e agentes de código: o caminho mais curto que funciona é um fetch de um componente React com a chave em uma variável VITE_ ou NEXT_PUBLIC_. Chaves roubadas então são revendidas para inferência em massa, como abordado no nosso post sobre chaves de API de IA roubadas e LLMjacking, e é na sua fatura que você percebe.
Como verificar se sua chave de API está exposta (2 minutos)

Faça essas verificações em produção, não em localhost.
- Observe a aba Network (45 segundos). Abra seu site, pressione F12 (Cmd+Option+I no Mac), escolha a aba Network e acione um recurso de IA, como enviar uma mensagem de chat. Filtre por
openai,anthropicougoogleapis. Se alguma requisição for direto paraapi.openai.com,api.anthropic.comougenerativelanguage.googleapis.com, clique nela e abra Headers. Uma chave emAuthorization: Bearer,x-api-key,x-goog-api-keyou em um parâmetro de consulta?key=significa que ela está exposta. - Pesquise no bundle (45 segundos). No DevTools, pressione Cmd+Option+F (Ctrl+Shift+F no Windows) para pesquisar em todos os scripts carregados. Pesquise por
sk-,sk-ant-,AIzaedangerouslyAllowBrowser. Qualquer ocorrência em um arquivo.jsservido ao navegador é um vazamento, mesmo que nenhuma requisição tenha sido disparada ainda. - Verifique os prefixos das suas variáveis de ambiente (30 segundos). Abra seu arquivo
.enve o painel da sua hospedagem. Ferramentas de build copiam algumas variáveis para o bundle do cliente por design:VITE_*no Vite (veja a documentação de env do Vite),NEXT_PUBLIC_*no Next.js,REACT_APP_*no Create React App eEXPO_PUBLIC_*no Expo. Uma chave de provedor atrás de qualquer um desses prefixos é pública, não importa como ela se chame.
Para uma varredura no terminal da saída do seu build:
# from your project root after a production build
grep -rnoE "sk-(proj-|ant-)?[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35}" dist/ build/ .next/static/ 2>/dev/null
grep -rn "dangerouslyAllowBrowser" dist/ build/ .next/static/ 2>/dev/null
Saída vazia é aprovação. O Google diz que novas chaves do AI Studio criadas desde 28 de maio de 2026 são "auth keys", cujo formato pode diferir de AIza, então pesquise também no bundle por generativelanguage.googleapis.com. Você só passa quando o navegador fala com o seu próprio domínio (por exemplo /api/chat) e nenhum hostname de provedor ou chave aparece em lugar nenhum.
O que fazer quando sua chave está exposta: ordem da correção
Rotacionar primeiro fecha o buraco de hoje; a mudança de arquitetura o fecha de vez.
| Etapa | O que você faz | Por que esta ordem | Tempo |
|---|---|---|---|
| 1. Rotacione a chave | Crie uma nova chave no console do provedor e então revogue a que vazou | A chave antiga pode já ter sido copiada; revogar é a única coisa que impede seu uso | 5 min |
| 2. Mova a chamada para o lado do servidor | Coloque a chamada ao provedor em uma rota de servidor ou função de edge que guarda a chave | Impede que a nova chave vaze da mesma forma | 30 a 60 min |
| 3. Adicione autenticação e limites de taxa por usuário | Exija uma sessão na rota e limite as requisições por usuário | Sem isso, seu proxy se torna um endpoint de IA gratuito para qualquer um | 30 min |
| 4. Defina limites de gasto no provedor | Defina orçamentos ou limites de gasto nas configurações de cobrança de cada provedor | Limita a fatura no pior caso se outra coisa vazar | 10 min |
| 5. Faça varredura no CI | Falhe o build se um padrão de chave aparecer no bundle de saída | Evita que o próximo recurso escrito pelo agente desfaça a correção | 15 min |
Implante a nova chave no lado do servidor antes de revogar a antiga, ou seu recurso de IA sai do ar. Nosso guia de boas práticas de rotação de chaves de API cobre rotação sem downtime. Na etapa 4, a orientação de segurança de chaves da OpenAI recomenda limites de gasto com aplicação rígida, porque apenas alertas não param o tráfego; um orçamento de cobrança do Google Cloud alerta você, mas não interrompe o uso por si só.
Como mover chamadas de OpenAI, Claude e Gemini para trás de uma rota de servidor

Uma rota de proxy de backend é um pequeno endpoint de servidor que recebe o prompt do usuário, adiciona a chave de API no servidor, chama o provedor e retorna apenas a resposta. O navegador nunca vê a chave. Aqui está uma rota Next.js mínima para OpenAI; Claude e Gemini seguem o mesmo formato:
// app/api/chat/route.ts (runs on the server only)
import OpenAI from "openai";
import { getSession } from "@/lib/auth"; // your auth helper
import { checkRateLimit } from "@/lib/limits"; // your per-user limiter
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // no NEXT_PUBLIC_ prefix
export async function POST(req: Request) {
const session = await getSession(req);
if (!session) return new Response("Unauthorized", { status: 401 });
if (!(await checkRateLimit(session.userId, 20))) { // 20 requests per minute
return new Response("Too many requests", { status: 429 });
}
const { message } = await req.json();
if (typeof message !== "string" || message.length > 4000) {
return new Response("Bad request", { status: 400 });
}
const response = await client.responses.create({
model: process.env.OPENAI_MODEL!, // pin the model server-side
input: message,
});
return Response.json({ reply: response.output_text }); // return the answer, nothing else
}
O que a torna segura, e não apenas escondida:
- O modelo fica no servidor. Se o navegador escolhe o modelo, um atacante escolhe o mais caro.
- A entrada tem limite. Nada de prompts gigantes pela sua conta.
- Só a resposta volta. Nunca retorne a resposta bruta do provedor ou o objeto de erro.
- Sem prefixo público.
OPENAI_API_KEYfica no servidor;NEXT_PUBLIC_OPENAI_API_KEYvai para cada visitante.
Para Claude e Gemini, use os SDKs deles com ANTHROPIC_API_KEY ou GEMINI_API_KEY. As restrições de site e IP do Google ajudam, mas qualquer pessoa com curl pode enviar qualquer cabeçalho Referer, então trate-as como uma segunda camada. O único caso justo para chamadas no navegador é uma ferramenta bring-your-own-key em que cada usuário cola a própria chave.
Deixe seu agente de código testar a rota de proxy com o Apidog CLI
Mover a chave é metade do trabalho; a outra metade é provar que ela continua movida depois do próximo recurso que seu agente escrever. Deixe o agente que construiu a rota testá-la com o Apidog CLI. Configure uma vez:
npm install -g apidog-cli@latest
apidog login --with-token <YOUR_TOKEN>
npx -y skills add https://apidog.com # pick your agent when prompted
Depois dê ao Claude Code, Codex, Cursor ou qualquer agente que você use um prompt:
Create Apidog test cases in project <id> for POST /api/chat on our staging
environment that assert:
1. a request without a session cookie returns 401,
2. a valid request returns 200 and the body does not contain "sk-", "sk-ant-",
"AIza" or "api_key",
3. the 21st request inside 60 seconds from the same user returns 429.
Run them and summarize any failures.
O agente lê o schema, valida seu payload, escreve os casos e os executa:
apidog test-case category --project <id>
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case.json
apidog test-case create --project <id> --file case.json
apidog run --test-case <caseId> --project <id> -e <envId> -r cli,json
A segunda asserção é a que mais importa: ela falha no momento em que um objeto de erro do provedor ou campo de debug começa a ecoar sua chave de volta para o cliente. Coloque o mesmo passo apidog run ... -r junit no seu pipeline para que cada deploy verifique novamente; nosso guia de CI/CD do Apidog CLI mostra a configuração, e Apidog CLI no Claude Code cobre o lado do agente. O plano Free do Apidog cobre até 4 usuários, então uma equipe pequena pode rodar isso sem assento pago.
Para o lado do repositório do mesmo problema, chaves commitadas no Git em vez de enviadas ao navegador, veja nossa verificação de vazamento de chaves de API com um scanner de segredos. Se seus agentes chamam ferramentas que lidam com dados de clientes, testar agentes de IA para vazamentos de dados cobre a próxima camada.
Erros comuns que mantêm a chave exposta
- Codificar a chave em Base64 ou dividi-la. O navegador precisa remontá-la para enviá-la, então o DevTools mostra o valor real.
- Movê-la para localStorage ou um cookie. Ela continua no cliente.
- Fazer proxy sem autenticação. Um
/api/chatpúblico é um endpoint de IA aberto na sua fatura. - Manter a chave antiga "só por precaução". Uma chave que ficou pública por um dia está copiada.
- Esquecer o app mobile. O guia do Google cita "apps web ou mobile" juntos por um motivo.
FAQ
Como sei se minha chave de API da OpenAI está exposta no navegador? Acione um recurso de IA com a aba Network do DevTools aberta. Uma requisição para api.openai.com com um cabeçalho Authorization: Bearer sk-... significa que ela está exposta. Pesquise também nos scripts carregados por sk- e dangerouslyAllowBrowser.
É seguro colocar uma chave de API em uma variável de ambiente NEXT_PUBLIC_ ou VITE_? Não. Esses prefixos existem para copiar valores para o bundle do cliente. Qualquer chave de provedor guardada neles vai para cada visitante. Use uma variável sem prefixo, apenas de servidor, e leia-a dentro de uma rota de servidor.
Restringir uma chave de API do Gemini ao meu site a torna segura no navegador? Ajuda, mas um script fora do navegador pode enviar qualquer cabeçalho Referer. O próprio guia do Google recomenda um proxy de backend para apps client-side.
Descobri que minha chave está exposta. O que devo fazer primeiro? Crie uma nova chave, implante-a no servidor e então revogue a antiga. Em seguida, mova a chamada para trás de uma rota autenticada com limites de taxa por usuário e defina limites de gasto no provedor.
Meu agente de código pode corrigir e testar isso para mim? Sim. Peça a ele para mover a chamada ao provedor para uma rota de servidor e então use o Apidog CLI para criar e executar testes que verificam 401 sem sessão, nenhum padrão de chave nas respostas e 429 ao ultrapassar seu limite de taxa.
