Pular para o conteúdo

Noções de segurança

Sua chave de API da OpenAI está exposta no frontend. Troque-a.

Uma chave de API da OpenAI exposta no frontend não pode ser presa a um domínio. Troque-a hoje, mova a chamada para o seu servidor e limite o gasto.

Vlad Tkachenko10 min de leitura
Um valor em formato de chave dentro de uma página de código de app, desenhado comprido o bastante para passar da borda direita do painel que o segura.

Em resumo

  • Uma chave de API da OpenAI exposta no seu frontend é o caso em que o alarme está certo. Nenhuma configuração deixa uma chave dessas segura dentro de um navegador.
  • Ela é um token ao portador, então basta tê-la em mãos. Não existe restrição de domínio que a prenda ao seu próprio site, como existe para uma chave do Google.
  • Troque-a hoje no painel da OpenAI, mova a chamada para trás de um endpoint seu e coloque um limite de gasto no projeto.
  • Encontramos uma em 33 de 30.998 apps escaneados. Raro, e as 33 saíram com nota D ou F.

Abra seu app num navegador, veja o código-fonte da página e procure por sk-proj-. Se voltar uma sequência comprida, sua chave de API da OpenAI está exposta no seu frontend, e qualquer visitante que você já teve poderia tê-la copiado.

Aqui está a parte que quase todo conselho sobre isso erra: uma chave da OpenAI não é uma chave do Google com outro prefixo, e nada que você configure num painel a deixa segura onde ela está. Uma chave do Google Maps tem lugar na sua página, e uma configuração gratuita a protege. Uma chave da OpenAI não tem em lugar nenhum uma configuração equivalente, e é por isso que a única primeira instrução honesta é trocá-la.

Entre 12 e 14 de agosto de 2026 rodamos nove verificações externas em 30.998 apps no ar, construídos com Lovable, Bolt, v0, Replit e Base44. Uma chave da OpenAI apareceu em 33 deles. Os 33 voltaram com nota D ou F, porque um único achado crítico limita a nota por mais que o app tenha acertado no resto. Os números completos estão no nosso relatório de varredura.

Uma chave de API da OpenAI exposta no frontend é um problema?

Sim. Este é o caso em que o alarme está certo.

Uma chave da OpenAI é um token ao portador, e a palavra carrega a explicação inteira: quem a porta pode usá-la. Ela viaja num cabeçalho que diz Authorization: Bearer sk-proj-…, e os servidores da OpenAI não perguntam mais nada. Nem qual site a enviou. Nem de que país ela veio, nem se quem manda é você.

Pense num bilhete de trem, e não num passaporte. Um cobrador não confere de quem é o nome num bilhete, porque tê-lo é a qualificação inteira. É isso que torna um bilhete digno de ser roubado e um passaporte quase nunca.

Então uma chave impressa no seu app é um bilhete que você entregou a cada visitante. A maioria nunca vai olhar. Quem olha em geral nem é gente: scrapers automáticos varrem páginas públicas coletando sequências em formato de chave, e não precisam saber quem você é para achar a sua.

Por que a variável de ambiente não escondeu a chave

Porque um build de frontend compila as variáveis de ambiente dentro do arquivo que ele publica.

Este é o passo que deixa as pessoas confiantes de que a chave está protegida. Você tirou a chave do código e pôs num arquivo .env, chamou de VITE_OPENAI_API_KEY ou NEXT_PUBLIC_OPENAI_API_KEY, e agora o código nomeia uma variável onde a chave estava. Nada mudou no app publicado. A ferramenta de build trocou essa variável pelo valor dela na saída, e o valor está no JavaScript que o seu visitante baixa.

A documentação do Vite diz isso sem rodeios: variáveis com o prefixo VITE_ ficam expostas no código-fonte do lado do cliente depois do bundling, e informações sensíveis como chaves de API não devem estar numa delas, porque os valores são empacotados dentro do seu código-fonte. Os prefixos VITE_ e NEXT_PUBLIC_ não são um cofre. São uma declaração de que você entendeu que essa variável é pública.

Uma variável de ambiente fez, sim, uma coisa real por você: manteve a chave fora do seu repositório, onde qualquer um que lesse seu código teria topado com ela. Seus visitantes nunca leem seu código. Eles leem o arquivo que seu build produziu a partir dele.

Por que você não consegue restringi-la como restringe uma chave do Google

Porque a OpenAI não oferece uma restrição desse tipo.

Se você leu algo sobre uma chave de API do Google num frontend, encontrou uma solução de cinco minutos: abra a chave no console do Google Cloud, defina uma restrição de referenciador HTTP, e a chave impressa na sua página funciona no seu site e devolve erro em qualquer outro. Esse conselho está certo para uma chave do Google, e ele não se transfere.

Não existe campo numa chave da OpenAI para "só a partir de yourapp.com". Nem lista de domínios permitidos, nem verificação de referenciador, nem restrição por IP que sobreviva a um navegador. O que a OpenAI dá no lugar fica na conta por trás da chave: a qual projeto ela pertence, quanto esse projeto pode gastar no mês e se a chave ainda existe. Isso limita quanto uma chave roubada pode custar. A chave em si continua funcionando de qualquer lugar até você apagá-la.

A faixa de cima é a solução sobre a qual falam com as pessoas. Na de baixo não há o que desenhar, porque a OpenAI não tem configuração dessa forma.

O que dangerouslyAllowBrowser realmente faz

Desliga uma proteção, e o nome dela é a documentação.

A biblioteca JavaScript oficial da OpenAI se recusa a rodar num navegador por padrão. O README diz que o suporte a navegador vem "disabled by default to avoid exposing your secret API credentials", e que ligar dangerouslyAllowBrowser "can be dangerous because it exposes your secret API credentials in the client-side code".

Se o seu app chama a OpenAI a partir do navegador, essa opção está como true em algum lugar do seu código, porque a biblioteca não inicia sem ela. Alguém digitou a palavra dangerously para calar uma mensagem de erro. É assim que esse achado costuma nascer, e a biblioteca avisou você primeiro.

A opção tem usos reais, e todos são estreitos: uma ferramenta interna em que você conhece cada usuário, uma chave de desenvolvimento temporária, uma chave tão limitada que gastá-la não custa nada. Um app público na internet aberta não é nenhum desses.

Quanto custa alguém achar sua chave

Uma fatura, e um app que para de funcionar.

A fatura é a parte que as pessoas esperam. Os pedidos de outra pessoa são cobrados da sua conta, e chamadas de modelo não são baratas para o padrão de um projeto paralelo. O jeito comum de quem cuida do app perceber é uma fatura maior que a do mês passado sem nenhum motivo que dê para apontar.

A queda é a parte que ninguém espera. Sua conta tem limites de taxa, e o tráfego de um estranho consome esses limites. Seu próprio app começa a devolver erros nas horas em que outra pessoa está trabalhando, e isso se lê como bug e não como roubo, então passa-se um dia depurando a coisa errada.

Há um terceiro custo, e ele depende de quão ampla era a chave. Uma chave de API autentica toda chamada que o projeto a que ela pertence pode fazer, então uma chave com permissões amplas alcança tudo o mais que estiver guardado ali: arquivos enviados para a conta, modelos ajustados, assistentes que você construiu. Verifique o que a chave conseguia de fato alcançar antes de decidir que isso era só sobre dinheiro.

Como chamar a OpenAI do seu app sem publicar a chave

Coloque algo seu entre o seu visitante e a OpenAI.

O formato é o mesmo em todo lugar. Seu app chama um pequeno endpoint que é seu. Esse endpoint guarda a chave, chama a OpenAI e devolve a resposta. O navegador nunca vê a chave, porque a chave nunca sai do seu servidor.

O mesmo pedido nas duas linhas. O que muda é de que lado do seu próprio servidor a chave está.

Onde esse endpoint mora depende do que você usou para construir:

  • Supabase na sua stack: uma Edge Function, com a chave guardada como secret no painel do Supabase.
  • Publicado na Vercel ou na Netlify: uma função serverless em /api, com a chave nas variáveis de ambiente de servidor do projeto.
  • Lovable, Bolt ou Replit: cada um tem seu próprio cofre de secrets. A regra não muda, e a armadilha também não: um cofre rotulado como secrets manda o valor para o navegador do mesmo jeito se o código que o lê roda lá.

Duas coisas valem a pena enquanto você está por lá. Dê ao seu endpoint um limite de taxa, porque um endpoint que chama a OpenAI para qualquer um que pedir é a mesma fatura por um caminho mais lento. E coloque um limite de gasto mensal no projeto da OpenAI, o único controle que põe um piso sob o pior caso.

Se o seu app usa a Realtime API da OpenAI para voz, o navegador precisa mesmo de uma credencial, e a OpenAI documenta como dar uma a ele: seu servidor cunha um client secret de vida curta e entrega esse à página. O bilhete continua existindo, expira em minutos, e foi o seu próprio servidor que o emitiu.

Como achar de graça toda chave que seu app publica

Comece na mão, porque custa cinco minutos e não exige instalar nada. Veja o código-fonte do seu site no ar e procure sk-proj- para uma chave da OpenAI, sk-ant- para uma da Anthropic, AIza para o Google e eyJ para um token do Supabase.

O que escapa assim é o JavaScript que a página carrega depois, que num app feito com vibe coding é quase tudo. Nosso scanner abre seu app num navegador de verdade, espera os bundles chegarem e lê esses em vez do HTML. Ele também decodifica cada token do Supabase e informa o papel escrito dentro dele, de modo que uma chave que tem lugar na sua página volta marcada como correta e não enterrada numa parede de vermelho.

A verificação de chaves é uma de nove, e as outras oito são o motivo pelo qual uma nota diz mais do que uma busca:

As nove verificações somente de leitura que uma varredura faz, na ordem da lista abaixo. Cada uma é lida de fora, do jeito que um estranho vê seu app.
O que a varredura olhaA pergunta que ela responde
Chaves secretas no seu códigoHá uma chave de API paga ou de admin legível por qualquer um?
Regras do banco de dadosUm estranho consegue ler as linhas dos seus usuários sem login?
Arquivos privadosArquivos .env ou dumps do banco podem ser baixados por uma URL?
Cabeçalhos de segurançaAs proteções do lado do navegador estão ligadas?
Buckets de armazenamentoQualquer um consegue listar os arquivos que seus usuários enviam?
Source mapsSeu código-fonte original está publicado junto com o app?
APIs abertas e CORSSeus endpoints respondem a qualquer site que pedir?
Validade do certificadoO HTTPS é válido e não está prestes a vencer nos seus visitantes?
Renovação do domínioO nome está renovado antes que outra pessoa possa tomá-lo?

Você recebe uma nota, uma pontuação e as contagens na tela em cerca de 20 segundos, sem conta. Dê um endereço de e-mail e a lista detalhada vem junto, com uma correção escrita para o seu builder que dá para colar direto.

Três coisas que ele não vai fazer, e são os motivos pelos quais é seguro apontá-lo para um app no ar: ele nunca faz login, nunca escreve nada e nunca guarda uma chave que encontra. Um segredo exposto é guardado como uma dica mascarada no formato sk-proj-…a1b2, e o valor real é descartado. Escaneie seu app, ou leia antes o que cada uma das nove verificações olha.

O que fazer agora

O que fazer

  • Troque a chave primeiro, no painel da OpenAI em API keys. Apagá-la do seu código não fecha nada, porque o valor antigo continua no seu histórico de versões e em cada cópia em cache da sua página.
  • Coloque um limite de gasto mensal no projeto a que a chave pertence. É o único controle que põe um teto no que este erro, ou um próximo, pode lhe custar.
  • Mova a chamada para trás de um endpoint seu, e dê a esse endpoint um limite de taxa próprio. Um navegador nunca deveria guardar uma chave que gasta dinheiro.
  • Leia sua página de uso nos dias em que a chave esteve ativa. Trocar a chave interrompe o que vem a seguir e não diz nada sobre o que já aconteceu.
  • Mantenha o prefixo VITE_ ou NEXT_PUBLIC_ longe de tudo o que você não gostaria que um estranho lesse. Esses prefixos querem dizer público, e sua ferramenta de build os leva ao pé da letra.

Se você prefere resolver tudo de uma vez, a checklist de segurança de 10 minutos cobre isso junto com as outras coisas que vale fechar num app recém-lançado. E para a pergunta mais ampla sobre quais chaves têm lugar num navegador, temos um guia para diferenciar chaves publicáveis das secretas e um censo do que 30.998 apps realmente publicaram.

Perguntas frequentes

Alguém achou minha chave da OpenAI no meu app. Qual é a primeira coisa a fazer?

Trocá-la. Abra seu painel da OpenAI em API keys, crie uma chave nova, coloque a nova no seu servidor e apague a antiga. Apagá-la do seu código não é a mesma coisa, porque o valor antigo continua no seu histórico de versões e em qualquer cópia em cache da sua página. Depois coloque um limite de gasto no projeto e leia sua página de uso nos dias em que a chave esteve ativa.

Posso restringir uma chave da OpenAI ao meu domínio, como faço com uma do Google?

Não. Uma chave de API do Google aceita uma restrição de referenciador HTTP que a faz funcionar no seu site e falhar em todos os outros, e é por isso que uma chave do Google na sua página costuma ser inofensiva. A OpenAI não oferece nada equivalente. Não há lista de domínios permitidos nem verificação de referenciador numa chave de API, então os únicos controles que você tem ficam na conta por trás dela: a qual projeto a chave pertence, quanto esse projeto pode gastar e se a chave ainda existe.

A biblioteca da OpenAI tem uma opção dangerouslyAllowBrowser. Isso deixa a chave segura?

Não, e o nome é o aviso. A OpenAI entrega o suporte a navegador desligado, e o próprio README diz que a opção é perigosa porque expõe suas credenciais secretas de API no código do lado do cliente. Ligá-la não muda o que o navegador consegue ler; só impede a biblioteca de se recusar a iniciar. Os casos estreitos para os quais ela foi pensada são ferramentas internas com usuários conhecidos e chaves de desenvolvimento de vida curta, não um app público.

Quanto alguém consegue gastar com uma chave que encontrou?

Tudo o que sua conta permitir, e é por isso que o limite de gasto importa mais do que o tamanho da fatura que você já viu. Uma chave roubada consome os mesmos limites de taxa que o seu app usa, então o primeiro sintoma muitas vezes nem é a fatura: é o seu próprio app começando a falhar enquanto outra pessoa está trabalhando. Coloque um limite de gasto mensal no projeto e você tem um teto para tudo isso.

Usei a chave só para uma demo rápida. Ainda assim importa?

Sim. Uma chave fica ativa até alguém revogá-la, e ela não sabe que deveria ser temporária. Scrapers automáticos coletam sem parar sequências em formato de chave em páginas públicas, então a idade da demo joga contra você e não a seu favor. Apagar a chave leva menos tempo do que decidir se valia a pena apagá-la.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Não sabe como está o seu próprio app?

Faça um scan gratuito e receba uma nota clara de A a F em cerca de 20 segundos. Sem conta e sem cartão.

Verificar meu app grátis

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.