El problema real#
Hace poco me di cuenta de que había pusheado un archivo .env con credenciales de API a un repositorio privado. Aunque fuera privado, no es excusa. Un acceso comprometido, un repositorio que se vuelve público, o simplemente una auditoría de seguridad hubiera expuesto mis tokens. Aprendí que no puedo confiar en eliminar archivos en commits posteriores—Git mantiene todo el historial.
Por qué git-filter-repo#
Hace años hubiera usado git filter-branch, pero es lento y propenso a errores. git-filter-repo es la herramienta moderna recomendada por los mantenedores de Git. Es rápido, preciso y tiene mejores opciones para este trabajo.
Instalación#
En mi servidor Debian:
apt-get install git-filter-repoO con pip:
pip3 install git-filter-repoPaso 1: Identificar los daños#
Primero, necesito saber qué commits contienen credenciales. Busco patrones sospechosos:
git log --all --oneline | head -20
git log -p --all | grep -i "token\|password\|api_key" | head -10También reviso qué archivos sensibles están en el historial:
git log --all --full-history -- ".env"
git log --all --full-history -- "config.yml"En mi caso, encontré que .env se había commitido en 3 ocasiones y un archivo credentials.json en 2.
Paso 2: Hacer backup#
Nunca hago esto sin backup:
cd /path/to/my/repo
git clone --mirror . backup-mirror.gitSi algo sale mal, tengo una copia completa del repositorio con todo su historial.
Paso 3: Limpiar archivos específicos#
Ejecuto git-filter-repo para eliminar los archivos sensibles del historial completo:
git-filter-repo --invert-paths --path .env --path credentials.jsonEl parámetro --invert-paths significa que mantiene todo EXCEPTO lo que especifico. Esto es lo contrario a lo que parece, pero funciona perfectamente.
El proceso tarda algunos segundos y reescribe todo el historial. Al final, veo:
Processed 47 commits
New history has 47 commitsPaso 4: Forzar el push (con cuidado)#
Como he reescrito el historial, necesito hacer un push forzado. En un servidor doméstico donde soy el único dev, es seguro:
git push origin --force --all
git push origin --force --tagsSi es un repositorio compartido, coordino con el equipo para que todos hagan git reset --hard origin/main después.
Paso 5: Rotar credenciales comprometidas#
Las credenciales que estaban en Git ya no están, pero debo asumir que fueron comprometidas. Roto todos los tokens:
- API Keys: Revoco las antiguas en el panel de la API y genero nuevas
- Contraseñas: Cambio la contraseña de cualquier servicio que usaba esas credenciales
- Tokens de BD: Regenero credenciales de bases de datos
- SSH Keys: Si estaban expuestas, genero nuevas parejas
Documento estos cambios en un archivo privado (no en Git):
2026-05-19 - Rotación de credenciales post-exposición
- API key antigua: revocada, nueva generada
- Token DB: regenerado
- Contraseña servicio X: cambiadaPaso 6: Prevención futura#
Agrego reglas a .gitignore (ahora está limpio):
.env
.env.local
credentials.json
secrets/También configuro un hook pre-commit para detectar patrones peligrosos:
git config core.hooksPath .githooksY creo .githooks/pre-commit:
#!/bin/bash
if git diff --cached | grep -iE "(password|token|api_key|secret)" && \
! git diff --cached | grep ".gitignore"; then
echo "⚠️ Posible credencial detectada. Abortando."
exit 1
fiConclusión#
La limpieza toma 10 minutos. La rotación de credenciales, otro rato. Pero es tiempo bien invertido. En un servidor doméstico, no tengo excusa para ser negligente con secretos. Ahora uso variables de entorno y archivos locales que nunca entran a Git.
Lección aprendida: Los secretos nunca van en control de versiones, ni siquiera “privado”. Punto.
## Equipamiento recomendado
- **[YubiKey 5 NFC](https://amzn.to/4dojPNk)** — Llave de seguridad física para SSH y GitLab — elimina el riesgo de tokens robados
- **[Disco duro externo 2TB](https://amzn.to/49gxgwl)** — Backup de repositorios antes de operaciones destructivas como git-filter-repo
*Enlaces de afiliado. Sin coste extra para ti.*