Segredo no Git: troque a chave antes de limpar o histórico
Saiba a ordem certa quando uma chave vai parar num commit: revogar e trocar primeiro, conferir o uso, só depois reescrever o histórico com git filter-repo e evitar a próxima.
Na revisão do pull request, alguém percebe que o arquivo .env entrou no commit, com a chave de produção do gateway de e-mail. O primeiro impulso é apagar: reescrever o histórico, forçar o push, sumir com a prova. Enquanto isso, a chave continua funcionando para quem já clonou, para o fork que alguém fez ontem e para o robô que varre repositórios públicos atrás de credenciais. A resposta curta: a chave morre primeiro. Revogue, gere outra, atualize os ambientes e confira se a antiga foi usada. Só depois, com calma e com a equipe, decida se vale reescrever o histórico.
Limpar o Git não desfaz a exposição. Trocar a chave desfaz. Se você só tiver tempo para uma das duas coisas, é a troca.
Por que limpar o histórico não basta
Um commit não mora num lugar só. No momento em que foi enviado, ele foi copiado para o servidor remoto e, a partir daí, para cada clone que fez git fetch, para forks, para caches de integração contínua, para espelhos. Num repositório público, ferramentas automatizadas leem os pushes em minutos procurando padrões de chaves conhecidas.
Reescrever o histórico muda o seu repositório e o remoto depois do push forçado. Não muda as cópias que já existem. Em serviços como o GitHub, commits antigos podem continuar acessíveis por referências de pull request ou pelo hash direto até que o suporte da plataforma limpe o cache. Ou seja, depois da limpeza você não tem como provar que ninguém mais tem a chave.
A chave trocada, por outro lado, é uma garantia que você controla. Uma vez revogada no provedor, a cópia que está em qualquer clone do mundo vira texto inútil. Por isso a ordem não é questão de estilo: o intervalo entre descobrir o vazamento e revogar é o intervalo de risco real.
A ordem certa, passo a passo
- Não repita o valor. Avise a equipe que a chave do serviço X vazou, sem colar o valor no chat, no ticket ou no e-mail.
- Revogue a chave no provedor e gere uma nova. Se houver risco de queda, aceite: aqui não cabe o período de convivência da rotação planejada.
- Atualize todos os ambientes com a nova chave, pelo cofre de segredos ou pelas variáveis de ambiente do deploy, e confirme que os serviços voltaram.
- Confira o uso da chave antiga nos logs ou no painel do provedor, desde o commit até a revogação. Chamadas de IPs desconhecidos viram incidente, com o tratamento que a sua política exigir.
- Tire o segredo do código atual: remova o arquivo do controle de versão, adicione ao
.gitignoree passe a ler de variável de ambiente. - Decida sobre o histórico: se o repositório é público ou será aberto, reescreva. Se é privado e a chave já está revogada, a reescrita é higiene, não urgência.
- Instale uma barreira para não acontecer de novo.
Para achar em que commits o valor aparece, use a busca por alteração do Git com um trecho da chave:
git log --all --oneline -S 'trecho-da-chave'
Para tirar o arquivo do controle de versão daqui para frente, sem apagá-lo do disco:
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Remove .env do controle de versão"
Isso não remove o arquivo dos commits antigos. Só impede que ele continue entrando.
Reescrever o histórico com git filter-repo
A ferramenta recomendada pelo próprio projeto Git para reescrever histórico é o git filter-repo, instalado à parte. Ele trabalha melhor num clone recém-feito, e avisa se você tentar rodar em outro. Duas formas comuns de uso:
# apagar um arquivo de todos os commits
git filter-repo --path .env --invert-paths
# ou trocar o valor da chave por um marcador em todo o histórico
echo 'valor-exato-da-chave==>CHAVE_REMOVIDA' > substituicoes.txt
git filter-repo --replace-text substituicoes.txt
rm substituicoes.txt
O git filter-repo remove o remoto origin depois de rodar, de propósito, para evitar um push acidental. Adicione de novo e force o envio:
git remote add origin git@github.com:empresa/projeto.git
git push --force --all
git push --force --tags
Todos os commits a partir do primeiro que tinha o segredo ganham hash novo. Quem tem clone antigo precisa clonar de novo; se fizer merge do clone antigo, o segredo volta. Combine o horário com a equipe, peça para ninguém dar push durante a troca e, se a plataforma mantiver referências de pull request com o commit antigo, peça ao suporte a limpeza.
Como evitar o próximo vazamento
A barreira mais eficaz é a que bloqueia antes do push. Plataformas como o GitHub oferecem varredura de segredos e proteção de push, que recusa o envio quando reconhece o padrão de uma chave conhecida. Localmente, ferramentas como o gitleaks rodam como hook de pre-commit e no pipeline de integração contínua. Nenhuma pega tudo, mas pegam o caso mais comum: o .env inteiro no commit.
O resto é hábito. Tenha um .env.example com nomes de variáveis e valores falsos, versionado, e o .env real sempre no .gitignore desde o primeiro commit. Em contêiner, o segredo entra em tempo de execução, não na imagem, como em segredo fora da imagem.
Erros comuns
- Limpar o histórico antes de revogar. A chave continua válida para todos os clones e forks que já existem.
- Colar a chave no chat para avisar. O segredo ganha mais uma cópia, num lugar com retenção e busca.
- Achar que
git rmresolve. O arquivo some do commit novo e continua em todos os anteriores. - Forçar o push sem combinar com a equipe. Alguém faz merge do clone antigo e o segredo volta ao histórico.
- Não olhar os logs da chave antiga. Um uso indevido no intervalo passa despercebido e o incidente não é tratado.
- Tratar repositório privado como cofre. Todo mundo com acesso, hoje e no futuro, tem a chave.
Checklist
- A chave exposta foi revogada no provedor.
- Os ambientes usam a chave nova e os serviços estão de pé.
- Os logs da chave antiga foram conferidos no intervalo de exposição.
- O arquivo com segredo está no
.gitignoree fora do controle de versão. - A decisão sobre reescrever o histórico foi tomada e comunicada.
- Existe varredura de segredos no push ou no pre-commit.
- Há um
.env.examplesem valores reais.
Pegue a última chave que você sabe que já passou por um commit, mesmo que tenha sido limpa depois. Ela ainda autentica em algum serviço? Se sim, a limpeza do Git não protegeu nada.
Referência: https://stackoverflow.com/questions/13716658/how-to-delete-all-commit-history-in-github