O que é e porque importa
Quando ligas um serviço à tua app, como uma base de dados, os pagamentos ou uma IA, dão-te uma chave. É uma sequência longa, e o mais cómodo é colá-la diretamente no código. Se a passares ao Claude no chat sem lhe dizer onde a pôr, às vezes ele também faz isso.
O problema é onde o código vai parar. Se o carregas no GitHub, a chave viaja com ele. Num repositório público qualquer pessoa a pode ler, e há programas que correm de propósito para apanhar chaves assim que aparecem. Quem a encontra usa-a em teu nome.
E apagá-la do código não chega. O git guarda a história toda: se a chave esteve num commit, nem que seja um só, nem que seja de há meses, continua lá. Quem descarrega o repositório volta a encontrá-la.
Nem todas as chaves são secretas. A pública do Supabase ou do Firebase é feita para estar na app: falo disso no guia PORTA (strikt.fit/porta). Aqui falo das outras: a chave secreta da base de dados, a da IA, a dos pagamentos, a dos emails. Essas ficam só no servidor, como no guia LIMITE (strikt.fit/limite).
1. A verificação
Antes de corrigir, vê o que lá está. Abre o Claude Code na pasta do projeto e cola:
Procura no projeto e em todo o histórico do git chaves, tokens e palavras-passe escritos no código, também nos ficheiros de configuração e nos commits antigos. Para cada um diz-me ficheiro, linha (ou commit) e serviço. Nunca mostres o valor.
«Todo o histórico», senão o Claude só olha para os ficheiros de agora. «Nunca mostres o valor», porque te basta saber onde está: a chave não deve aparecer no chat.
Uma segunda opinião: gitleaks
Um programa feito de propósito reconhece a forma de muitíssimos tipos de chave. Eu uso o gitleaks, que é open source. No Mac instalas com o Homebrew:
brew install gitleaks
Depois, dentro da pasta do projeto:
gitleaks git -v --redact gitleaks dir -v --redact
- gitleaks git analisa todo o histórico, commit a commit.
- gitleaks dir analisa os ficheiros de agora, também os que o git não segue.
- --redact esconde os valores nos resultados, -v mostra os detalhes.
No GitHub
Nos repositórios públicos o secret scanning do GitHub é gratuito e automático: se encontra a chave de um serviço que conhece, põe um alerta no separador Security and quality. A push protection vai mais longe: bloqueia o push que leva uma chave, antes de chegar à internet. Para os teus próprios push para repositórios públicos está ativa por predefinição; no repositório ativas em Settings → Advanced Security. Nos repositórios privados poderá exigir uma funcionalidade paga do GitHub.
2. As chaves no .env
O sítio certo para uma chave é um ficheiro .env que o git nunca carrega. Funciona assim:
- As chaves vão num ficheiro .env na pasta do projeto: NOME_CHAVE=valor.
- No ficheiro .gitignore acrescenta a linha .env: o git nunca o carrega.
- No código fica só o nome: process.env.NOME_CHAVE (o teu framework ou o dotenv lê o ficheiro).
- Onde publicas a app, as mesmas variáveis vão nas definições do alojamento.
O prompt para o Claude o fazer:
Tira as chaves do código: põe-nas no .env, acrescenta .env ao .gitignore, cria um .env.example só com os nomes e verifica que nenhuma chave secreta acaba no código do browser.
O .env.example, esse sim, carregas: só tem os nomes, tipo AI_API_KEY= sem nada a seguir. Assim, mais à frente, tu e quem trabalha contigo sabem que chaves são precisas sem ver nenhuma.
Se o .env já estava no repositório
O .gitignore só vale para os ficheiros que o git ainda não segue. Se o .env já tinha sido carregado, o .gitignore não chega: corre git rm --cached .env, que o tira do repositório e o deixa no computador, e depois faz um commit. A seguir muda as chaves que lá estavam, porque ficam no histórico: vê a parte 4. Se quiseres, pede ao Claude para o fazer e te explicar cada passo.
3. As variáveis que vão parar ao browser
É aqui que mais se cai. Pôr uma chave no .env não a torna secreta: o que conta é onde corre o código que a usa. O que corre no servidor continua a ser teu. O que chega ao browser, ou à app no telemóvel, qualquer pessoa vê, com .env ou sem ele.
Alguns frameworks mandam de propósito para o browser as variáveis com um certo prefixo:
- Next.js: NEXT_PUBLIC_. O valor é escrito dentro do JavaScript que chega ao browser.
- Vite: VITE_. A documentação do Vite diz explicitamente para não pôr lá chaves.
- Expo: EXPO_PUBLIC_. O valor fica em texto simples na app compilada.
Uma chave secreta com um destes prefixos é pública, mesmo que esteja no .env. Acontece assim: a chamada à IA a partir do browser não funciona, junta-se o prefixo, agora funciona, e a chave saiu. A solução certa é passar a chamada para o servidor.
Se não queres que o Claude leia o .env
O Claude Code pode ler os ficheiros da pasta, o .env incluído. Se preferires que não, acrescenta uma regra de bloqueio em .claude/settings.json, na pasta do projeto:
{
"permissions": {
"deny": ["Read(./.env)"]
}
}Põe-na depois de mudares as chaves: bloqueia também a escrita desse ficheiro. Vale para as ferramentas com que o Claude lê ficheiros e para comandos como o cat, não para um script que abre o ficheiro por conta própria: para isso é preciso a sandbox do Claude Code.
4. Se já te fugiu uma
Se encontras uma chave secreta num commit, no GitHub ou no código que vai para o browser, dá-a como queimada. Não sabes quem já a copiou. Hoje, por esta ordem:
- No site do serviço revoga a chave e cria uma nova. É o passo que conta: quem a copiou ainda a pode usar.
- Põe a nova só no .env e nas definições do alojamento.
- Verifica no serviço os consumos estranhos.
- Depois, com calma, limpa o histórico.
Se a app está online podes criar a chave nova um pouco antes de revogar a antiga, para não ficar parada. Mas a antiga revogas hoje, não amanhã. Para o último passo, pede primeiro uma explicação:
Uma chave foi parar a um commit antigo. Explica-me como a tirar do histórico do git antes de mexer no que quer que seja.
Porquê esta ordem. Tirar uma chave do histórico quer dizer reescrever a história do repositório (o GitHub recomenda o git-filter-repo) e carregá-la com um force push, e as cópias antigas e os forks ainda a têm. Por isso o GitHub põe como primeiro passo revogar a chave, e diz que às vezes chega: uma chave revogada já não abre nada.
Os erros mais comuns
- Apagar a chave do código e ficar por aí. Continua no histórico: revoga-a e cria uma nova.
- Limpar o histórico antes de revogar a chave. Revoga-a logo: a limpeza vem depois.
- Acrescentar o .env ao .gitignore quando já tinha sido carregado. Tira-o com git rm --cached .env e um commit, depois muda as chaves que lá estavam.
- Pôr uma chave secreta numa variável NEXT_PUBLIC_, VITE_ ou EXPO_PUBLIC_. Vai parar ao browser ou à app: passa a chamada para o servidor.
- Colar as chaves novas no chat com o Claude. Põe-nas tu no .env e diz-lhe só o nome da variável.
Checklist
- Verificação feita aos ficheiros e ao histórico, com o Claude e com o gitleaks.
- As chaves secretas estão só no .env e nas definições do alojamento.
- O .env está no .gitignore e não está no repositório.
- Há um .env.example com os nomes, sem valores.
- Nenhuma chave secreta em variáveis NEXT_PUBLIC_, VITE_ ou EXPO_PUBLIC_.
- Cada chave fugida foi revogada e substituída.
- Push protection ativa no repositório do GitHub.
Perguntas frequentes
O meu repositório é privado. Tenho de fazer isto na mesma?
Sim. Se um dia o tornas público ou acrescentas alguém, o histórico vai inteiro, chaves incluídas. E as variáveis que vão parar ao browser são públicas de qualquer forma.
A chave pública do Supabase numa variável NEXT_PUBLIC_ está bem?
Sim, é feita para estar no browser. O que protege os dados são as regras na base de dados, como explico no guia PORTA. A chave secreta (secret ou service_role), essa nunca.
Já revoguei a chave. Tenho de limpar o histórico na mesma?
Nem sempre: uma chave revogada já não abre nada. Se no histórico há outra coisa sensível, como dados de pessoas, ou não tens a certeza de ter revogado tudo, limpa, a começar pelo prompt da parte 4.