Eliminar secretos del historial de Git tras una filtración de prototipo de IA
Aprende a eliminar secretos del historial de Git con git filter-repo o BFG, rotar claves filtradas y confirmar que tu repo está limpio antes de volver a desplegar.

Qué significa realmente una clave filtrada en Git
Un "secreto" es cualquier cosa que concede acceso si otra persona lo obtiene: claves de API, tokens de acceso, contraseñas, cadenas de conexión a bases de datos, certificados de firma y claves privadas. Si sirve para iniciar sesión, cobrar una tarjeta, leer datos o desplegar código, trátalo como un secreto.
Cuando un secreto se compromete en Git, pasa a formar parte del historial del repositorio. Borrar el archivo más tarde (o añadirlo a .gitignore) no elimina el commit anterior que aún contiene el valor. Cualquiera con acceso al repo, a un fork, a un clon antiguo o a una copia en caché todavía puede encontrarlo. Por eso eliminar secretos del historial es un trabajo distinto a borrar el archivo.
Los prototipos construidos con IA filtran secretos de maneras previsibles. Un patrón común es cometer un archivo .env durante la configuración inicial y olvidarlo. Otro es pegar un fragmento que funciona desde el panel del proveedor en el código "solo para probar". Los logs de depuración pueden ser igual de malos si imprimen tokens y luego se comiten con todo lo demás.
Lo que está en riesgo es más que la vergüenza: acceso no autorizado a servicios (bases de datos, almacenamiento, proveedores de auth), facturación inesperada por APIs abusadas, exposición o modificación de datos de clientes, y pérdida de confianza si usuarios o socios se enteran.
Un ejemplo concreto: un prototipo usa una clave real de Stripe, la comete una vez y luego cambia a una clave nueva en un commit posterior. Aunque el código actual parezca correcto, la primera clave sigue en el historial, esperando a ser copiada.
Detener la filtración primero
En el momento en que un secreto llega al historial de Git, trátalo como expuesto. Incluso si el repo era privado, puede que ya lo haya copiado el clon de un compañero, un runner de CI, una caché de build o una herramienta de IA que tiró el código. No esperes a confirmar. Actúa como si un atacante ya lo tuviera.
Objetivo inicial: inutilizar la clave filtrada. Revócala, desactívala o bórrala lo antes posible. Si el proveedor permite limitar permisos, puedes reducirlos inmediatamente como paso temporal, pero planifica rotar la credencial por completo después de la limpieza.
A continuación, detén todo lo que pueda seguir usando o propagando el secreto. Pausa jobs de CI, workflows programados, entornos de preview y despliegues automáticos. Estos suelen leer variables de entorno y producir logs que pueden volver a filtrar valores.
Antes de tocar el historial de Git, alinea al equipo:
- Revocar o deshabilitar la credencial expuesta.
- Pausar CI/despliegues y decir a todo el mundo que no haga push, merge ni tag hasta tener el plan claro.
- Capturar el valor exacto y dónde apareció (nombre de archivo y hash del commit) para poder eliminar lo correcto.
- Mantener una nota de incidente privada: qué se rotó, cuándo y qué servicios podrían verse afectados.
Ejemplo: si tu prototipo cometió una clave de Stripe y CI ejecuta tests en cada push, la pipeline puede seguir llamando a Stripe con la clave filtrada y dejar trazas en logs. Pausa la pipeline, revoca la clave y luego pasa a reescribir el historial.
Inventario de lo que debes purgar
Antes de reescribir el historial, especifica qué se filtró y dónde aparece. Así evitas una "limpieza" que deje el problema real intacto.
Anota cada valor filtrado que conozcas: claves de API, URLs de bases de datos, secretos JWT, secretos de cliente OAuth, tokens de webhook. Si solo tienes una captura de pantalla o un log de error, extrae el texto exacto si puedes.
Luego mapea cada secreto a sus ubicaciones en el repo. No mires solo la rama actual. Los secretos suelen vivir en commits antiguos, archivos de pruebas, salidas de depuración, datos exportados e incluso capturas de pantalla.
Lugares comunes para revisar: variantes de .env, archivos de configuración (como settings.json o docker-compose.yml), carpetas de logs y tmp, datos de seed/sample y docs/asset que puedan contener capturas.
Una forma rápida de buscar una cadena conocida en tu copia de trabajo:
git grep -n "PASTE_PART_OF_KEY_HERE" -- .
Decide qué necesitas eliminar:
- Cadenas exactas (mejor cuando un token aparece en varios sitios)
- Archivos enteros (útil para
.envo datos exportados que nunca debieron estar en Git) - Carpetas completas (útil para
logs/,tmp/, backups accidentales)
Ejemplo: un prototipo generado por IA puede haber cometido .env y también pegado una URL de base de datos de producción en config.ts. Eso son dos objetivos: eliminar .env de todo el historial y quitar la cadena URL exacta donde aparezca.
Finalmente, haz una copia de seguridad segura del repositorio antes de reescribir historial. Copia la carpeta entera o crea un mirror clone para poder recuperar si la reescritura sale mal.
Paso a paso: purgar con git filter-repo
Si necesitas reescrituras flexibles y fiables (múltiples ramas, tags, historial raro o más de un secreto), git filter-repo suele ser la mejor opción.
Parte desde un clon fresco y haz una copia de seguridad. Cierra cualquier PR abierto que pueda reintroducir los commits viejos.
1) Eliminar un archivo en todas partes (ejemplo: un archivo env)
Si un prototipo cometió accidentalmente .env (o un JSON de credenciales), púrgalo por ruta:
git filter-repo --force --invert-paths --path .env
Eso reescribe cada commit y elimina el archivo dondequiera que apareciera.
2) Eliminar o reemplazar cadenas secretas por contenido
Cuando los secretos están incrustados en código, usa un archivo de reemplazos. Crea replacements.txt así (lado izquierdo es lo que buscar, lado derecho es lo que escribir):
AKIAIOSFODNN7EXAMPLE==\u003eREMOVED
"api_key": "sk-live-123"==\u003e"api_key": "REMOVED"
Luego ejecuta:
git filter-repo --force --replace-text replacements.txt
Antes de reescribir, decide qué ramas y tags incluirás. Por defecto, filter-repo reescribe lo que esté en tu clon local, así que haz fetch de todo lo que planeas mantener (incluyendo tags) y elimina las ramas que no quieras publicar.
Tras la reescritura, confirma que nada se rompió localmente. Ejecuta tu build y tests, arranca el servidor y haz una comprobación rápida end-to-end (por ejemplo, un flujo básico de login). Luego busca otra vez patrones de claves antiguas.
Paso a paso: purgar con BFG Repo-Cleaner
BFG Repo-Cleaner encaja bien cuando quieres una limpieza rápida, especialmente para borrar archivos enteros como .env o creds.json, o para sustituir cadenas de claves conocidas.
Antes de empezar, haz un mirror clone para reescribir todas las refs (ramas y tags) de forma segura:
# 1) Mirror clone (works best for history rewrites)
git clone --mirror \u003cyour-repo-url\u003e repo.git
cd repo.git
# 2) Remove whole files everywhere in history
bfg --delete-files .env
bfg --delete-files creds.json
# 3) Or replace leaked text (use a rules file)
# lines like: OLD_SECRET==\u003eREMOVED
bfg --replace-text replacements.txt
# 4) Prune old objects BFG made unreachable
git reflog expire --expire=now --all
git gc --prune=now --aggressive
BFG reescribe commits que contenían el secreto, creando un historial nuevo donde esos blobs o cadenas han desaparecido. No rota credenciales y no te protege de copias del repo que alguien ya clonó.
Valida el resultado antes de pushear nada. Busca tokens exactos que esperas que hayan desaparecido, comprueba que archivos sensibles ya no existen en ningún commit e inspecciona también los tags.
Pushear el historial reescrito sin causar caos
Después de reescribir el historial, el push es donde los equipos suelen llevarse sorpresas. El objetivo es simple: publicar el historial limpio y luego evitar que alguien vuelva a introducir los commits antiguos.
Coordina una congelación corta (15 a 60 minutos) donde nadie haga push, merge ni abra ramas nuevas. Decide qué necesitas reescribir: normalmente main y las ramas de larga vida que la gente usa. Las ramas de feature antiguas a menudo se pueden eliminar en lugar de reescribir.
Una secuencia segura:
- Anuncia la congelación y confirma que todos están en pausa.
- Relaja temporalmente reglas de protección de ramas si bloquean force pushes.
- Force-pushea las ramas reescritas (y tags si hace falta).
- Vuelve a habilitar las protecciones de rama enseguida.
- Di a todos exactamente cómo sincronizar sus clones locales.
Como los IDs de commit cambiaron, normalmente necesitarás un push forzado. Da a los colaboradores dos opciones: re-clonar (lo más simple) o resetear hard (más rápido, pero fácil de romper). Por ejemplo:
git fetch --all --prune
git checkout main
git reset --hard origin/main
También limpia donde los secretos suelen quedarse: clones locales antiguos, caches de CI, artefactos de build y repos espejo.
Rotar credenciales de forma segura
Una vez que una credencial se ha cometido, trátala como comprometida. Rota todas las credenciales filtradas que puedas encontrar, no solo la que disparó la alarma.
Lista todo lo que pueda dar acceso: claves de API, contraseñas de bases de datos, archivos de cuentas de servicio, secretos de cliente OAuth, claves de firma JWT, credenciales SMTP, secretos de firma de webhooks y cualquier clave "de prueba" que apunte a sistemas reales. Si el prototipo tocó producción en algún momento, inclúyelo en el plan de rotación.
Crea nuevas credenciales con el principio de menor privilegio. Prefiere tokens con scopes limitados y vidas cortas cuando el proveedor lo permita.
Un orden seguro:
- Crea nuevos secretos primero y desplíégalos en apps y CI.
- Actualiza producción, staging y configuraciones de desarrollo local.
- Confirma que la app funciona end-to-end.
- Revoca los secretos viejos al final y luego monitoriza errores.
Tras la rotación, mantén los secretos fuera del repo para siempre. Usa variables de la plataforma de hosting o un gestor de secretos, y guarda secretos locales en un archivo env no comiteado. Añade medidas como un escaneo de secretos en pre-commit y chequeos en CI.
Lleva un registro simple de qué cambió, cuándo, dónde se guarda el nuevo valor y quién es su responsable.
Verificar que el repo está realmente limpio
Asume que no has terminado hasta demostrar que la filtración se ha ido por completo de todos los lugares desde los que un desarrollador pueda obtenerla.
Busca el valor filtrado exacto, no solo el nombre de archivo. Ejecuta la búsqueda en todas las refs (ramas y tags), no solo en tu rama actual.
git fetch --all --tags --prune
git grep -n "PASTE_LEAKED_VALUE_HERE" $(git rev-list --all)
Luego haz un escaneo más amplio buscando formas comunes de secretos. Busca claves privadas (BEGIN PRIVATE KEY), cadenas largas tipo base64, JWTs (tres bloques separados por puntos) y cualquier prefijo específico de proveedor que reconozcas.
No olvides logs de CI y artefactos de build. Aunque el repositorio esté limpio, un secreto filtrado puede seguir en logs de CI, capas caché de Docker o artefactos subidos. Vuelve a ejecutar la última pipeline después de rotar credenciales para comprobar que los logs ya no imprimen valores sensibles.
Finalmente, haz una prueba de clonación fresca en una máquina limpia o en una carpeta nueva. Un repo puede parecer limpio localmente mientras una ref remota antigua todavía exponga el secreto.
mkdir /tmp/clean-test && cd /tmp/clean-test
git clone \u003cyour-remote\u003e
cd \u003crepo\u003e
git log --all -S "PASTE_LEAKED_VALUE_HERE" --oneline
Si sigues encontrando trazas después de una reescritura, normalmente significa que un tag, una rama remota o un artefacto de CI aún conserva el contenido viejo.
Errores comunes que vuelven a introducir secretos
La mayoría de las filtraciones regresan porque la limpieza fue a medias. Borrar un archivo en el commit más reciente no es lo mismo que quitarlo del historial. Si alguien aún puede hacer checkout de un commit antiguo, el secreto sigue expuesto.
Dos olvidos comunes:
- Olvidar tags y ramas antiguas. Un secreto puede desaparecer en
mainpero seguir vivo en un tag de release o en una rama obsoleta. - Eliminar un archivo pero no el valor. Los prototipos de IA suelen copiar la misma clave en varios sitios: archivos de configuración, fixtures de test, scripts de build y logs.
Otra causa frecuente es que un compañero haga push desde un clon antiguo tras la reescritura. Coordina un momento para que todos re-clonen o reseteen, y bloquea merges hasta que eso ocurra.
Ejemplo: filtración de un prototipo de IA y un plan de limpieza realista
Una historia común: un fundador construye un prototipo rápido en Lovable o Replit y comete todo a Git sin notar que se coló un .env. La demo funciona y luego algo parece raro.
Lo primero que suele aparecer es un efecto real: una factura de cloud que sube de madrugada, alertas de inicio de sesión extrañas desde un proveedor de correo, o un dashboard de API mostrando tráfico de países a los que no se atiende.
Si el repo es público, asume que las claves ya se copiaron. Rota credenciales inmediatamente y luego reescribe el historial para quitar el archivo y cualquier token pegado. Si el repo es privado, haz el mismo trabajo y además revisa quién tuvo acceso (contratistas antiguos, máquinas compartidas, logs de CI).
Si no sabes qué clave se filtró, actúa como si todas lo hubiesen hecho. Lista cada sistema que el prototipo tocó (base de datos, proveedor de auth, pagos, email, almacenamiento) y rota en un orden que no te deje fuera.
Un plan realista:
- Congelar despliegues y pausar CI para que nada siga difundiendo el valor.
- Rotar primero las credenciales más peligrosas (admin de cloud, base de datos, claves de pago).
- Reescribir el historial para quitar
.envy tokens comiteados, luego force-push. - Actualizar la app para leer secretos solo desde una configuración segura (vars de entorno o un gestor de secretos).
- Verificar el resultado con un clon fresco y búsquedas en todo el historial.
La comunicación importa. Informa a las partes interesadas en términos claros: qué ocurrió, qué se expuso, qué rotaste y qué verificaste. Da una línea de tiempo breve y una nota clara sobre qué cambia para ellos.
Checklist rápido antes de reanudar desarrollo
Tras una filtración, el objetivo es sencillo: inutilizar el secreto robado, borrarlo del historial y probar que ha desaparecido antes de seguir escribiendo código.
- Revocar primero, luego pausar trabajo. Desactiva la clave/token filtrado y detén pushes durante la limpieza.
- Inventariar lo filtrado. Lista cada archivo y patrón que pueda contener credenciales.
- Reescribir el historial una sola vez, con una herramienta. Elige
git filter-repoo BFG y ejecútalo en un clon fresco. - Pushear el historial nuevo y resetear a los colaboradores. Force-pushea ramas/tags reescritos y luego pide a todos re-clonar (o hacer hard reset).
- Rotar y reubicar. Crea nuevas credenciales, saca secretos del repo y añade controles para evitar repeticiones.
Haz una comprobación final: clona en una carpeta totalmente nueva y escanea ese clon. Si el escaneo está limpio y la app funciona con las credenciales nuevas, vuelves a una base segura.
Pasos siguientes si el repo está hecho un lío o la app ya está rota
A veces el problema es más grande que una reescritura de historial. Si el repo está enmarañado, el build falla o ni siquiera sabes qué se filtró, es fácil perder días en cirugía de Git mientras la app sigue inestable.
Señales de que necesitas ayuda: múltiples repos y forks que no puedes rastrear, secretos duplicados en archivos generados, despliegues que fallan tras la reescritura, sospecha de filtraciones múltiples o una base de código difícil de cambiar con seguridad (estructura enredada, sin tests, ediciones aleatorias de IA).
Si heredaste un prototipo generado por IA, esto es exactamente para lo que sirve FixMyMess (fixmymess.ai): diagnosticar la base de código y el historial, reparar problemas de lógica y seguridad (como auth roto o secretos expuestos) y dejar la app lista para desplegar de forma limpia. Ofrecen una auditoría de código gratuita para mapear las exposiciones antes de que te comprometas, y la mayoría de los trabajos de remediación se completa en 48-72 horas.
Preguntas Frecuentes
If I deleted the `.env` file, why is the secret still “leaked”?
Significa que el secreto ahora forma parte del historial del repositorio, no solo de los archivos actuales. Aunque borres el archivo o lo añadas a .gitignore, cualquiera que pueda acceder a un commit antiguo, un fork o un clon anterior aún puede recuperar el valor.
What should I do first when I discover a secret in Git history?
Revoca o desactiva la credencial expuesta de inmediato para que el valor filtrado deje de funcionar. Luego pausa CI/despliegues que puedan seguir usándola o registrándola, y solo después reescribe el historial de Git para purgar el secreto de commits anteriores.
Is it still dangerous if the repo was private?
Sí. Los repos privados también se copian: clones de compañeros, runners de CI, caches de builds y máquinas compartidas. Trata cualquier secreto comprometido en Git como expuesto y rótalo, incluso si crees que el acceso fue limitado.
Should I use `git filter-repo` or BFG Repo-Cleaner?
Para la mayoría de los casos, git filter-repo es la opción predeterminada más segura porque es flexible con múltiples ramas, tags e historiales complejos. BFG suele ser más rápido y sencillo para borrar archivos completos o reemplazos de texto directos, pero igual debes validar tags y ejecutar pasos de limpieza después.
Why do I need to force-push after cleaning the repo?
Porque reescribir el historial cambia los IDs de los commits, así que los commits limpiados son “diferentes” a los antiguos. Planea una ventana corta de congelación, haz el push forzado de las ramas reescritas y luego pide a todos que re-clonen o hagan reset hard para que nadie vuelva a subir los commits viejos.
How can I confirm the secret is truly gone after the rewrite?
Busca en todas las referencias, no solo en la rama actual, y busca el valor exacto filtrado así como patrones comunes de secretos. Luego clona de nuevo en una carpeta limpia y vuelve a buscar; un clon fresco es la forma más fácil de detectar refs remotas o tags que aún contengan el secreto.
If I rotate the key, do I still need to remove it from Git history?
Porque no puedes probar de forma fiable quién ya lo vio; clones o logs pueden contenerlo. La rotación hace inútil el valor robado; la limpieza del historial evita descubrimientos futuros y reusos accidentales, pero no revierte la exposición pasada.
What usually causes secrets to come back after a cleanup?
Los errores más comunes son limpiar solo main y olvidar tags o ramas antiguas que aún referencian el commit viejo, o que un compañero haga push desde un clon antiguo después de la reescritura. Coordina un momento para que todos re-clonen o hagan reset.
How do I prevent AI-generated prototypes from leaking secrets again?
No subas archivos .env, y carga secretos desde variables de entorno o un gestor de secretos que ofrezca tu plataforma de hosting. Añade un escaneo de secretos en pre-commit y checks en CI para evitar que un token pegado o un log de depuración se cuele en un commit.
Can FixMyMess help if the repo is tangled or I’m worried I’ll break things?
Sí. Si la base de código está desordenada, los builds fallan o los secretos están duplicados en ficheros generados, la reescritura del historial puede ser arriesgada y lenta. FixMyMess puede auditar el repo, identificar exposiciones, limpiar el historial de forma segura, rotar credenciales sin romper despliegues y dejar la app lista para publicar rápidamente.