git rm não apaga: por que a chave que você removeu continua no repositório
Apagar a linha e commitar por cima não remove nada — o commit anterior continua lá, e é ele que a varredura acha. Entenda por que reescrever o histórico raramente resolve, por que rotacionar é a única correção de verdade, e o que uma varredura honesta precisa te dizer sobre o que ela olhou.
Você viu a chave no arquivo. Apagou a linha, commitou "remove api key", subiu.
O arquivo está limpo. O repositório não.
O Git não guarda a última versão dos seus arquivos — guarda todas as versões. O commit em que a chave existia continua exatamente onde estava, alcançável por qualquer pessoa com acesso ao repositório e por um comando de uma linha. Remover é um evento novo na linha do tempo, não uma borracha passada sobre o passado.
Por que o segredo continua lá
Um repositório Git é uma cadeia de fotografias. Cada commit fotografa a árvore inteira do projeto naquele instante, e as fotografias antigas não são alteradas quando você tira uma nova — se fossem, o hash mudaria e a cadeia quebraria. É essa imutabilidade que faz o Git ser confiável, e é a mesma imutabilidade que preserva a sua chave.
Qualquer pessoa com o repositório clonado faz isto:
git log -p --all -S "sk-proj-"
E recebe o commit, a data, o autor e a chave em texto puro. Não é técnica avançada: é a busca por conteúdo que o Git oferece de fábrica, e ela varre todos os commits de todos os branches.
Vale para repositório privado também. "Privado" descreve quem pode clonar hoje — não quem clonou ontem, não o repositório que ficou público por uma semana em 2024, não o fork que um freelancer fez enquanto tinha acesso, não a cópia no CI. A superfície de um segredo commitado é todo mundo que teve acesso desde o commit, não quem tem acesso agora.
Então é só reescrever o histórico?
Existe a ferramenta: git filter-repo (ou o antigo filter-branch) reescreve os commits e remove o conteúdo. Ela funciona. E, na prática, resolve menos do que parece:
- Todo hash de commit muda a partir do ponto reescrito. Na prática, todo mundo do time precisa reclonar; quem fizer
git pullcria uma bagunça de histórico duplicado. - Pull requests, issues e code reviews que citam os commits antigos passam a apontar para o nada.
- Forks e clones não são reescritos. Você não tem acesso a eles.
- O GitHub mantém objetos órfãos acessíveis por um tempo depois do push forçado, e commits de PR podem seguir alcançáveis por URL direta mesmo depois do rebase.
- Backups, mirrors e caches de CI continuam com a versão antiga.
Ou seja: reescrever o histórico é higiene, não remediação. Faz sentido fazer — depois. Não é o que protege você.
A única correção que funciona
Rotacionar a chave. Sempre. Antes de qualquer outra coisa.
A pergunta certa não é "consegui apagar?", é "essa credencial ainda serve para alguma coisa?". Se a resposta for não, o que sobrou no histórico é uma string sem valor. Se for sim, ela continua sendo uma chave válida publicada num lugar que você não controla mais.
A ordem prática, em qualquer incidente de segredo commitado:
- Revogar a credencial no provedor. Isso encerra o incidente.
- Emitir a nova e colocá-la onde ela deveria estar (variável de ambiente do servidor, secret manager, secret do CI — nunca no repositório).
- Auditar o uso no período entre o commit e a revogação. Quase todo provedor tem log de acesso: AWS CloudTrail, painel de uso da OpenAI, log de API do Stripe. É onde você descobre se foi só um susto.
- Só então decidir se vale reescrever o histórico.
O passo 3 é o mais pulado e o mais importante. É a diferença entre "vazou uma chave" e "vazou uma chave e alguém usou por onze dias" — e, se houver dado pessoal envolvido, é o que sustenta a decisão de comunicar titulares e ANPD, que é obrigação de LGPD e não de opinião.
Por que o .gitignore não te salvou
O .gitignore só impede que arquivos ainda não rastreados entrem. Se o .env foi commitado uma vez, adicioná-lo ao .gitignore depois não faz nada: o Git continua rastreando o que já rastreava.
Os três caminhos que mais vemos para um segredo entrar num repositório de app vibecoded:
.envcommitado no primeiro dia, antes de existir.gitignore— o assistente cria o arquivo, ogit add .leva tudo.- Arquivo de exemplo preenchido com valor real — o
.env.exampleque virou.env.examplecom a chave de verdade "só pra testar". - Chave colada direto no código durante uma depuração, revertida no arquivo mas não no histórico. Este é o mais comum quando o desenvolvimento é conversacional: pede-se para "testar com a chave real", funciona, e a reversão acontece no arquivo — nunca no commit.
O que uma varredura de segredo precisa te dizer
Aqui está o detalhe que separa relatório útil de relatório decorativo, e é onde vale desconfiar de qualquer ferramenta: "nenhum segredo encontrado" só significa alguma coisa se vier acompanhado do que foi olhado.
Varredura de segredo costuma clonar o repositório de forma rasa — só os últimos N commits, só o branch principal — porque clonar histórico completo de todo mundo é caro. Isso é uma escolha legítima. O que não é legítimo é declarar "repositório limpo" quando o que se olhou foram os últimos 50 commits de um branch. O segredo que interessa costuma ter sido commitado no começo do projeto, justamente onde a varredura rasa não chega.
Foi essa distância que a gente fechou no Sentinela, e vale explicar porque é exatamente o que você deve cobrar de qualquer ferramenta que use:
- O clone traz o histórico completo do branch quando o repositório cabe no limite de tamanho. Acima dele, cai para o clone raso — e o achado diz qual dos dois aconteceu, com o branch e o commit exato que foi varrido.
- O achado "limpo" é qualificado pelo escopo: limpo dentro do que foi olhado, nunca "seu repositório não tem segredo".
- Varredura que falhou (ferramenta estourou o tempo, saída ilegível) vira um achado visível de severidade alta, nunca um relatório limpo silencioso. Varredura que não rodou não é varredura sem problema.
- As exclusões que você configura para o SAST não valem para a varredura de segredo. Excluir uma pasta do scan de código é razoável; excluí-la do scan de segredo esconderia um segredo real embaixo de um relatório que diz "limpo".
O que o Sentinela varre no seu repositório
Conectando o repositório (GitHub, GitLab ou Bitbucket), a auditoria clona uma cópia temporária e roda cinco checagens de caixa-branca:
| Checagem | O que procura |
|---|---|
| Gitleaks | Segredo no histórico de commits — o assunto deste post |
| Semgrep | Padrão de código inseguro: SQL montado por concatenação, XSS, desserialização, entre outros |
| Trivy | Configuração insegura de infraestrutura como código |
| Hadolint | Dockerfile: usuário root, imagem sem tag fixa, segredo em ENV |
| Workflows do GitHub Actions | Permissão excessiva, pull_request_target mal usado, ação de terceiro sem pin |
O clone é temporário e apagado ao fim do run. O segredo encontrado nunca é gravado em claro: o achado guarda no máximo os quatro primeiros caracteres, e o valor cru só existe em memória durante a varredura, o tempo de produzir a máscara.
E, de novo, o que a ferramenta não faz: não testa se a chave ainda é válida (essa checagem exige mandar a credencial do cliente para o provedor, e a gente decidiu não fazer isso), não revoga nada por você, e não roda na sua máquina como hook de pre-commit.
Um segredo commitado é uma corrida
Entre o commit e a revogação existe uma janela, e o tamanho dela é a única variável que você controla. Descobrir na auditoria da semana que vem é melhor que não descobrir — mas é uma janela de sete dias.
Dá para encurtar isso para minutos, e é sobre isso o último post da série. Se você caiu aqui direto, o primeiro trata da metade que está visível de fora: a chave que foi parar no bundle JavaScript.
Como ligar a varredura no seu repositório
A conexão de repositório Git está no plano Business. É o que liga as cinco checagens da tabela acima, mais o alerta por push, mais as auditorias ilimitadas que fazem isso rodar a cada deploy em vez de quatro vezes por mês.
Assinar o Business e conectar o repositório leva alguns minutos: você autoriza a conexão no seu provedor, aponta o repositório, e a primeira varredura roda em seguida.
Descubre en 30 segundos lo que tu dominio está mostrando.
Comprobación pasiva, sin tocar tu servidor. Sale una nota y una lista de hallazgos por gravedad — y tú decides si quieres seguirlo cada mes.