<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Palavras-passe no histórico do git: como as removi (e porque não chega)

Estava a fazer uma revisão geral do meu projeto, à procura de coisas esquecidas. Encontrei uma que preferia não ter encontrado: um ficheiro de configuração de deploy com a chave que assina os tokens de login, a palavra-passe do administrador e a palavra-passe para o envio de emails. Em texto simples, no histórico do git, em três branches.

Como foi possível

O ficheiro estava no .gitignore. O problema é que foi lá parar depois de ter sido feito commit uma primeira vez, e o .gitignore só ignora ficheiros que ainda não são seguidos: um ficheiro já seguido continua a sê-lo, alteração após alteração, mesmo que o nome esteja na lista.

O repositório era privado, mas privado não significa seguro: veem-no os colaboradores, os serviços de CI, os backups e todos os computadores onde foi clonado.

Removê-lo de todo o histórico

Apagar o ficheiro com um novo commit não serve: ele continua em todos os commits anteriores. É preciso reescrever o histórico de todos os branches. Usei git filter-repo num clone mirror, depois fiz push forçado e verifiquei que nenhum commit o continha:

# 1. Stop tracking the file (it stays on disk)
git rm --cached .env.deploy
git commit -m "Stop tracking .env.deploy"

# 2. Rewrite the history of EVERY branch on a mirror clone
git clone --mirror git@github.com:me/my-repo.git
cd my-repo.git
git filter-repo --path .env.deploy --invert-paths
git push --force --mirror

# 3. Verify: no commit on any branch may still contain it
git log --all --oneline -- .env.deploy

Depois de uma reescrita destas, quem tiver uma cópia local tem de a realinhar com o novo remoto: os commits antigos não podem voltar a ser publicados.

Porque não chega

Aqui está a lição mais importante. Mesmo depois da reescrita, os commits antigos podem continuar acessíveis: o GitHub, por exemplo, mantém referências internas aos pull requests que um push forçado não consegue apagar. E não se sabe quem descarregou o repositório no passado.

Remover um segredo do histórico serve para não piorar a situação; a única coisa que a resolve é mudá-lo. Na prática:

  • Nova chave de assinatura dos tokens: invalida todas as sessões existentes, por isso todos terão de voltar a fazer login. É o preço a pagar.
  • Nova palavra-passe de administrador.
  • Revogação da palavra-passe da aplicação de email e criação de uma nova.
  • Atualização das variáveis em todos os ambientes onde são usadas.

A segunda vez, o mesmo padrão

Algumas semanas depois encontrei o mesmo problema noutra forma: um script de instalação do servidor, muito prático, que escrevia as credenciais diretamente no ficheiro de configuração. É o padrão recorrente: o script de conveniência que guarda os segredos para não ter de configurar nada. Reescrevi-o para gerar as chaves no momento e pedir as palavras-passe a quem o executa:

# Before: secrets written into a script tracked by git
# JWT_SECRET=hardcoded-value...

# After: generated or asked at runtime, never committed
JWT_SECRET="$(openssl rand -base64 48)"
read -rsp "SMTP password: " SMTP_PASS; echo
umask 077   # the .env file is readable by its owner only
printf 'JWT_SECRET=%s\nSMTP_PASS=%s\n' "$JWT_SECRET" "$SMTP_PASS" > .env

Os hábitos que evitam que volte a acontecer

  1. Um .env.example no repositório, com os nomes das variáveis e sem valores; o .env verdadeiro nunca entra no repositório.
  2. Um scanner de segredos antes de cada commit, como o gitleaks num hook de pre-commit: bloqueia o commit antes de o mal estar feito.
  3. A proteção de push oferecida pelo GitHub e pelo GitLab, que rejeita pushes com segredos reconhecíveis.
  4. Nunca segredos em scripts: gerá-los em runtime ou lê-los do ambiente.
  5. Uma verificação periódica do histórico: um segredo encontrado por ti é um incidente pequeno, um encontrado por outros não.

Em resumo

Um segredo num repositório deve ser tratado como comprometido no momento em que o encontras. Reescrever o histórico é necessário mas não suficiente: a verdadeira correção é mudar todas as credenciais expostas. Depois, convém tornar o problema impossível de repetir, com scanners e regras claras. Reuni mais práticas de segurança neste guia.

💬 Notas dos leitores

0 notas

Escreva uma nota

Partilhe a sua opinião, uma sugestão ou um elogio

Notas recentes

Ainda não há notas. Seja o primeiro a comentar!