O segredo que saiu do índice e continuou no histórico
Dogwalk·

O segredo que saiu do índice e continuou no histórico

6 min de leitura← Voltar para timeline

O arquivo que saiu do índice

No dia 18 de outubro, às 11h45, o Dogwalk recebeu um commit que não mudava uma linha da aplicação. O commit 16406aa — chore(security): remove .env com segredos do indice do git — mexia em 6 arquivos: 84 linhas adicionadas, 39 removidas. Nenhum componente, nenhuma rota, nenhum teste.

O que ele tirou do versionamento foram cinco arquivos de ambiente: desenvolvimento, local, produção, teste e um quinto que guardava a configuração do serviço de autenticação. Esse último carregava um token com papel administrativo de serviço — o tipo que ignora as regras de acesso por linha. O projeto daquele token já tinha sido desativado meses antes, mas o valor continuava sentado no repositório, esperando alguém clonar.

O problema é o que o commit não fez. E isso não é um defeito do commit: é o comportamento do Git.

O que saiu e o que ficou

A remoção foi feita pelo índice, não pelo disco. Os cinco arquivos continuam onde estavam, com as permissões que tinham — e as permissões contam uma história: todos os cinco estavam no repositório com o bit de execução ligado, modo 100755. Ninguém marca um arquivo de ambiente como executável de propósito. É resultado de um chmod recursivo em algum diretório esquecido.

O .gitignore cresceu de verdade nesse commit. Entre as regras novas ficaram duas que importam juntas:

.env*
!.env.example

A primeira cobre tudo que começa com .env. A segunda é a exceção que salva o projeto: sem a negação, o arquivo-modelo também seria ignorado, e o próximo desenvolvedor perderia o mapa de quais variáveis existem. O .env.example continua rastreado, com 3,6 KB de nomes e comentários, sem valor nenhum dentro — exatamente como deve ser.

Por que o gancho local não viu

O repositório tem um gancho de pré-commit que roda checagem rápida de tipos e lint dos arquivos alterados. Não roda detector de segredo — então um arquivo de ambiente entrava no índice sem ninguém perguntar nada.

O detector existia, com um allowlist bem pensado: isentava o arquivo-modelo e os caminhos gerados de build e de teste, onde há tokens de mentira circulando em fixtures. O allowlist estava certo. O que faltava era o detector rodar no momento em que o arquivo entrava — não numa varredura posterior do repositório.

O achado veio de uma varredura posterior: quando apareceu, o arquivo já estava versionado, copiado em cada clone e presente em três commits. Um gate que roda depois do commit é um relatório, não um gate.

Parar de rastrear não é apagar

Depois do commit, perguntei ao repositório quantos objetos de arquivo de ambiente ainda estavam alcançáveis:

18

Dezoito blobs. Cinco arquivos, mas dezoito versões históricas deles. O blob específico daquele arquivo com o token administrativo continua no banco de objetos, íntegro, com 569 bytes, e aparece em três commits: a remoção de 18 de outubro e as duas entradas anteriores, de 21 de maio e 11 de junho.

Ou seja: git rm --cached resolve o futuro e não toca no passado. O valor continua extraível por qualquer pessoa com acesso ao repositório, com um comando de duas palavras.

A partir daí a decisão se divide em duas perguntas independentes, e a ordem entre elas não é negociável:

  1. O segredo ainda vale? Se vale, a resposta é rotação imediata da credencial. Girar a chave resolve o risco hoje e não depende de reescrita nenhuma.
  2. Vale reescrever o histórico? Isso é limpeza, não contenção. Reescrever troca todos os identificadores de commit, obriga cada clone a ser refeito e quebra referências em issues e pull requests. Faz sentido quando o valor é sensível e ainda é válido — nunca como substituto da rotação.

No caso deste projeto, a credencial era de um serviço desativado. Não havia o que rotacionar. O histórico ficou como estava: reescrever toda a linha do tempo custava mais que o risco de um token morto.

O que isso muda

Duas coisas, nenhuma delas específica do Dogwalk.

A primeira é que “removi o segredo do repositório” precisa ser uma frase mais longa. Removi do índice, ou removi do histórico? São operações diferentes, com custos diferentes, e a segunda não substitui a rotação.

A segunda é que o lugar do detector é o gancho. Um scan posterior encontra o que já vazou; o gancho impede que entre.

E o arquivo-modelo precisa sobreviver ao bloqueio: .env* sem a negação correspondente resolve o vazamento e apaga a documentação das variáveis, transformando cada ambiente novo em tentativa e erro.

Próximo passo

O passo que falta é medir o que ainda está alcançável em cada repositório, não só neste, e classificar cada achado em duas colunas: credencial viva e credencial morta. Viva rotaciona primeiro. Morta entra na fila de limpeza, com o custo de reescrita na balança.

# o commit que tirou os arquivos do índice (e o que ele mudou)
git log -1 --stat 16406aa

# o blob continua no banco de objetos, com 569 bytes
git cat-file -s 8a86f50

# quantos objetos de arquivo de ambiente ainda são alcançáveis
git rev-list --all --objects | grep -E '\.env\.' | wc -l

Três comandos, nenhum deles destrutivo, e todos respondendo à mesma pergunta: o que ainda dá para ler se alguém clonar este repositório hoje.

Leia também

Mesmo projeto, mesma trilha — as histórias se conectam.

Próximo passo

Se isto te serviu, tem mais: escrevo sobre o mesmo assunto nos outros projetos, e a loja tem as ferramentas que eu uso aqui.

Sem spam, descadastro de um clique. Também estou aqui: Loja de skills · Portfólio