git filter-repo: eliminar credenciales del historial de Git

Al auditar el historial de un repositorio con gitleaks apareció una credencial hardcodeada en un commit antiguo. El fichero ya no existía en HEAD, pero el secreto seguía accesible en el historial. Hay que reescribir la historia.


Por qué git filter-branch no es la solución

La primera búsqueda suele llevar a git filter-branch. Funciona, pero trae tres problemas importantes:

  1. Es lento — en repos con cientos de commits puede tardar minutos.
  2. Deja refs/original/ — Git hace una copia de seguridad de las refs originales. Hay que borrarlas manualmente o el secreto sigue siendo accesible localmente.
  3. El propio Git lo desaconseja — desde Git 2.36 el comando muestra este aviso:
WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites. Hit man git-filter-branch for full details. New users are
         strongly encouraged to use git filter-repo instead.

La alternativa oficial es git filter-repo.


Instalación de git filter-repo

No viene incluido en Git. La forma más rápida en macOS:

brew install git-filter-repo

En Linux:

pip install git-filter-repo

Limpiar el historial con —replace-text

git filter-repo permite sustituir texto en todos los blobs del historial. Hay que crear un fichero de sustituciones con el formato literal:VALOR_A_REEMPLAZAR==>REEMPLAZO:

echo "literal:mi-api-key-secreta==>REMOVED" > replacements.txt
git filter-repo --replace-text replacements.txt

Para múltiples secretos, cada uno en una línea:

literal:api-key-12345==>REMOVED
literal:otro-secreto==>REMOVED

git filter-repo reescribe todos los commits donde aparece ese texto, en cualquier fichero. El resultado es un historial limpio con los hashes cambiados.


Re-añadir el remoto

git filter-repo elimina el remoto por seguridad — evita que un force push accidental sobreescriba el repositorio sin que sea una acción deliberada. Hay que añadirlo de nuevo:

git remote add origin git@github.com:org/repo.git

Force push de todas las ramas afectadas

Como los hashes han cambiado, hay que hacer force push de cada rama que se quiera actualizar:

git push origin develop --force
git push origin main --force

⚠️ Si hay otros colaboradores trabajando en el repositorio, hay que coordinarlo — sus clones locales tendrán el historial antiguo y necesitarán hacer git fetch --all y resetear sus ramas locales.


Verificación con gitleaks

Una vez hecho el push, conviene verificar que el secreto ya no aparece en ningún punto del historial:

gitleaks detect --source . --log-opts="--all"

Si la salida no muestra hallazgos, el historial está limpio.


Rotar las credenciales aunque el historial esté limpio

Limpiar el historial de Git no es suficiente por sí solo. Si el secreto estuvo alguna vez en un repositorio remoto — público o privado — hay que asumir que fue comprometido.

GitHub, GitLab y la mayoría de proveedores escanean push events en busca de secretos conocidos. Es probable que ya hayan notificado al proveedor del servicio. La acción obligatoria es revocar la credencial y generar una nueva, independientemente de cuánto tiempo haya estado expuesta.

El orden correcto es siempre: primero rotar, luego limpiar.

Alberto Rivas

© 2026 albrivas

LinkedIn GitHub