Ir al contenido

Cómo limpiar credenciales expuestas en Git con git-filter-repo y rotar tokens

·3 mins
Rogelio Guerra Riverón
Autor
Rogelio Guerra Riverón
Construyendo mi propia infraestructura web desde cero. Aquí documento cada paso: servidores, redes, contenedores y lo que vaya surgiendo.

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-repo

O con pip:

pip3 install git-filter-repo

Paso 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 -10

Tambié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.git

Si 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.json

El 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 commits

Paso 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 --tags

Si 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:

  1. API Keys: Revoco las antiguas en el panel de la API y genero nuevas
  2. Contraseñas: Cambio la contraseña de cualquier servicio que usaba esas credenciales
  3. Tokens de BD: Regenero credenciales de bases de datos
  4. 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: cambiada

Paso 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 .githooks

Y 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
fi

Conclusió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.*