Ir para o conteúdo
CodeAuditAgent
Todos os artigos

Segredos vazados no histórico do Git: encontrar, rotacionar, prevenir

Apagar uma chave de API commitada não a remove do histórico do Git. Como achar segredos vazados, rotacioná-los e reescrever o histórico com segurança.

· 7 min de leitura · Lina Source LLC

Alguém commita um arquivo .env, ou cola uma chave de API ativa num módulo de configuração para testar algo rápido. Alguém percebe na revisão, a chave é apagada no commit seguinte e todo mundo segue em frente. A chave continua lá. O Git guarda todas as versões de todos os arquivos, e qualquer um que consiga clonar o repositório pode ler o commit que a adicionou.

Credenciais fixas no código são rastreadas como CWE-798. Este guia cobre o que fazer quando uma delas já caiu no histórico: encontrar todos os segredos, rotacioná-los antes de qualquer outra coisa, decidir se vale reescrever o histórico e montar as barreiras que impedem o próximo caso.

Por que apagar a linha não basta

Um commit que remove um segredo adiciona um novo snapshot sem ele. O snapshot anterior, com o segredo, continua referenciado pelo histórico da branch. O git log -p mostra o segredo, um git checkout do commit antigo o restaura, e todo clone e fork existente já tem uma cópia. Fazer squash da branch antes do merge também não ajuda se os commits originais chegaram a ser enviados: o remoto pode ainda guardá-los, e quem fez fetch deles os tem localmente.

Em um repositório público, presuma que o segredo foi visto. Scrapers automatizados vigiam os pushes públicos em busca de padrões de credenciais, e a janela entre um push e o primeiro abuso pode ser muito curta. Em um repositório privado a exposição é menor, mas não é zero: toda pessoa funcionária, prestadora, todo sistema de CI e toda integração com acesso de leitura tem o segredo.

Passo 1: rotacione primeiro

A rotação é o único passo que de fato remove o risco. Reescrever o histórico esconde o segredo de clones futuros; não faz nada quanto às cópias que já existem. Então revogue e substitua a credencial antes de tocar no repositório.

Planeje a rotação para que ela não cause uma indisponibilidade. Muitos provedores permitem que duas chaves sejam válidas ao mesmo tempo: crie a nova, implante em todos os lugares onde a antiga era usada, confirme que o tráfego migrou e então revogue a antiga. Se o provedor só permite uma chave, aceite uma interrupção curta; alguns minutos fora do ar são uma troca melhor do que manter ativa uma credencial sabidamente vazada enquanto você coordena uma virada perfeita.

  • Gere uma chave nova no console do provedor e implante-a pelo seu caminho normal de configuração.
  • Revogue a chave antiga. Não basta parar de usá-la; uma chave válida sem uso continua sendo uma chave válida.
  • Verifique os logs de auditoria do provedor em busca de atividade com a chave antiga desde a data do commit: chamadas de API, usuários novos, permissões alteradas, cobranças inesperadas.
  • Se o segredo era uma senha de banco ou uma chave de assinatura, pense no que ele poderia ter destravado. Um segredo de assinatura JWT vazado significa que qualquer token pode ter sido forjado, então invalide as sessões existentes.
  • Registre o que foi exposto, por quanto tempo e o que você mudou. Você vai querer isso se um cliente ou auditor perguntar.

Passo 2: encontre tudo que vazou

Onde há um segredo commitado costuma haver outros. Vasculhe o histórico completo, não só a árvore atual, e inclua todas as branches e tags.

# Commits que adicionaram ou removeram uma string em qualquer ponto do histórico
git log -p -S "sk_live_" --all

# Busca por regex nos diffs de todos os commits
git log -p -G "AKIA[0-9A-Z]{16}" --all

# Arquivos que já existiram em um caminho suspeito
git log --all --oneline -- .env config/production.json

# Scanners dedicados checam centenas de formatos conhecidos de credenciais
gitleaks git -v .
trufflehog git file://. --only-verified

O gitleaks e o trufflehog são scanners de código aberto feitos para esse trabalho. Eles conhecem os formatos das chaves dos provedores mais comuns, e o trufflehog consegue verificar se uma credencial encontrada ainda está ativa. Versões mais antigas do gitleaks usam gitleaks detect --source . em vez do subcomando git. Rode um scanner uma vez sobre o histórico completo e depois mantenha-o rodando nos commits novos. Espere alguns falsos positivos, como fixtures de teste e chaves de exemplo na documentação. Revise cada um e registre os falsos positivos confirmados na allowlist ou no arquivo de baseline do scanner, para que a próxima execução mostre apenas achados novos.

Não esqueça os lugares ao redor do repositório: logs de CI que ecoaram variáveis de ambiente, comentários de issues e pull requests, páginas de wiki, camadas de imagens Docker construídas a partir do repositório, e gists ou trechos compartilhados durante uma depuração.

Passo 3: decida se vale reescrever o histórico

Uma vez rotacionado, o segredo é inútil, então reescrever o histórico é limpeza e não contenção. Ainda assim vale a pena quando o repositório é público, quando o segredo revela algo além de si mesmo (um hostname interno, o nome de um cliente), ou quando a conformidade exige. Isso tem custos reais: toda pessoa colaboradora precisa clonar de novo, pull requests abertos são atrapalhados e os hashes dos commits mudam. Tudo o que referencia um commit por hash, como notas de versão, registros de deploy, links em issues ou dependências fixadas em outros repositórios, vai apontar para commits que não existem mais na branch. Anuncie a reescrita com antecedência, escolha um momento tranquilo e faça merge ou feche os pull requests abertos antes.

O git filter-repo é a ferramenta que o projeto Git recomenda para isso, no lugar do antigo git filter-branch. Ele trabalha sobre um clone novo e remove o remoto origin como medida de segurança, então você o adiciona de volta antes de fazer o push.

# Comece a partir de um clone novo
git clone [email protected]:acme/api.git api-clean
cd api-clean

# Opção A: remover um arquivo de todos os commits
git filter-repo --invert-paths --path config/production.env

# Opção B: substituir as strings do segredo onde quer que apareçam
# replacements.txt contém uma regra por linha, por exemplo:
#   COLE_A_CHAVE_ANTIGA_AQUI==>REDACTED
git filter-repo --replace-text ../replacements.txt

# filter-repo remove o origin; adicione de volta e faça force-push
git remote add origin [email protected]:acme/api.git
git push --force --all
git push --force --tags

O que a reescrita não consegue fazer

  • Ela não alcança clones existentes, forks, caches de CI ou backups. Quem fez fetch antes da reescrita continua com os commits antigos.
  • No GitHub, as referências criadas para pull requests são somente leitura e podem manter commits antigos acessíveis. Remover as visualizações em cache e essas referências exige contato com o Suporte do GitHub.
  • Pessoas colaboradoras que fizerem pull ou merge a partir de um clone antigo podem empurrar o histórico antigo de volta. Peça que todo mundo clone de novo e proteja a branch enquanto isso acontece.
  • Ela não remove o segredo de nenhum outro lugar para onde ele foi copiado: logs, tickets, mensagens de chat ou artefatos construídos.

É por isso que a ordem importa. Rotacione, depois limpe. Um histórico reescrito com uma chave ativa é uma falsa sensação de segurança. Depois do push, rode seu scanner sobre um clone novo para confirmar que o segredo realmente sumiu de todas as branches e tags.

Passo 4: evite o próximo

O objetivo é tornar difícil commitar um segredo e rápido perceber quando acontece. Nenhum controle isolado faz as duas coisas, então combine-os em camadas.

Mantenha os segredos fora do caminho do código

Leia credenciais de variáveis de ambiente ou de um gerenciador de segredos em tempo de execução, e valide na inicialização que as necessárias estão presentes. Commite um .env.example com valores de exemplo e coloque o .env no .gitignore já no primeiro commit. Para produção, um gerenciador de segredos como AWS Secrets Manager, Google Secret Manager, HashiCorp Vault ou as configurações de ambiente criptografadas da sua plataforma oferece controle de acesso, logs de auditoria e rotação mais fácil.

Bloqueie segredos antes que sejam commitados

Um hook de pre-commit pega o erro na máquina de quem desenvolve, antes que ele chegue ao remoto. O framework pre-commit reduz isso a algumas linhas de configuração que toda pessoa colaboradora consegue instalar.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: vX.Y.Z  # fixe na tag da versão mais recente
    hooks:
      - id: gitleaks

# Instale uma vez por clone
#   pip install pre-commit
#   pre-commit install

Hooks são locais e podem ser pulados, então rode o mesmo scanner no CI a cada push como rede de segurança. No GitHub, ative o secret scanning e a push protection onde seu plano permitir; a push protection rejeita, do lado do servidor, pushes que contenham tokens reconhecidos de provedores.

Reduza o dano dos vazamentos que você não perceber

  • Restrinja cada chave às permissões mínimas e, onde o provedor permitir, a IPs ou referrers específicos.
  • Prefira credenciais de vida curta, como federação OIDC do CI para o seu provedor de nuvem, em vez de chaves estáticas de vida longa.
  • Use chaves separadas por ambiente, para que uma chave de teste vazada não consiga tocar produção.
  • Mantenha um runbook de rotação para cada segredo crítico, para que rotacionar sob pressão seja rotina em vez de improviso.

Onde a revisão de código entra

Scanners casam bem com formatos conhecidos; são mais fracos no contexto, como uma senha montada a partir de duas constantes ou uma credencial padrão que vem num fallback de configuração. Uma passada de revisão sobre o código pega esses casos. O CodeAuditAgent sinaliza credenciais fixas no código como CWE-798 com a evidência citada quando audita a branch padrão de um repositório público ou um trecho colado. Ele lê o código atual, não o histórico de commits, então mantenha um scanner de histórico no lugar para os commits passados.

Em resumo: um segredo commitado é um segredo vazado. Rotacione hoje, limpe o histórico se valer a perturbação, e faça o próximo vazamento falhar no hook de pre-commit em vez de numa página pública.