Subiste el .env a GitHub: qué pasa en los primeros minutos
Borrar el archivo no borra la key. Qué hacer, en qué orden, y por qué rotar va antes que limpiar el historial.
En este artículo
Pasa así: armás un proyecto chico, ponés la API key en un .env, te olvidás del .gitignore y hacés git push. Diez minutos después te das cuenta, borrás el archivo, commit, push. En la pestaña de GitHub el .env ya no está.
Y sin embargo la key sigue ahí. No es un bug de GitHub ni mala suerte: es cómo funciona Git. Este artículo es sobre esos minutos y sobre qué hacer, en orden.
El archivo que borraste #
Git no guarda archivos sueltos: guarda una foto del proyecto entero en cada commit. Cuando borrás el .env, creás una foto nueva sin el archivo. Las anteriores siguen existiendo, y la pestaña de GitHub te muestra solo la última, HEAD.
Fijate en qué fotos queda el .env. Con las flechas podés volver al primer commit, y el último paso muestra qué se lleva alguien que clona el repo.
4 de 5Borrás el .env. La foto nueva, HEAD, no lo tiene. Las dos anteriores sí.
Un clone trae todas las fotos, no solo la última. Cualquiera puede buscar el archivo en el historial y leerlo:
$ git log --all --oneline -- .env
e05d2a8 borro el .env
7c2e4b1 conecto la API
$ git show 7c2e4b1:.env
OPENAI_API_KEY=sk-proj-7Hq2…Quién mira un push público #
Un repo público no es público solo para personas. Hay scanners que leen el feed de eventos públicos de GitHub y buscan cadenas con forma conocida: AKIA en las keys de AWS, ghp_ en los tokens de GitHub, sk- en varios proveedores de IA. No necesitan entrar a tu repo: les llega el diff.
La pregunta útil no es si alguien la vio. Es cuánto tiempo pasó entre el push y el momento en que vos te diste cuenta: esa es la ventana de exposición. Mové tu marca y mirá qué alcanzó a pasar.
Cuando te diste cuenta, alguien la venía usando hace 8 min. 2 usos antes de que reaccionaras.
Del otro lado, GitHub tiene secret scanning: para algunos proveedores le avisa al emisor de la key, que puede revocarla sola. Para otros no pasa nada. Por eso lo que importa es qué podía hacer esa key.
| Secreto | Forma | Avisa GitHub | Qué hacer primero |
|---|---|---|---|
| Access key de AWS | AKIA… | sí | Desactivarla en IAM y mirar CloudTrail |
| Token de GitHub | ghp_… | sí | Revocarlo y generar otro con scopes mínimos |
| Key de IA | sk-… | depende | Revocarla y poner límite de gasto |
| Contraseña de la base | ninguna | no | Cambiarla y revisar conexiones |
Rotar antes que limpiar #
El instinto es reescribir el historial para que no quede rastro. Para mí ese es el error más común: se siente como arreglarlo, y no lo es. Mientras reescribís, la key sigue siendo válida en cada copia que ya existe, y ninguna reescritura llega hasta ahí.
Probá los dos órdenes. La key vive en el proveedor; las líneas rojas son las copias que todavía pueden usarla. Rotar la mata en todas a la vez.
copias con la key válida: 3. minutos con la key activa: 0.
1 de 4Te das cuenta. La key es válida en las tres copias.
- Revocar o rotar la key en el proveedor. Antes que cualquier otra cosa.
- Revisar el uso desde el momento del push: facturación, CloudTrail, el panel que tenga el proveedor.
- Sacar el archivo del historial, recién ahora.
- Frenar el próximo push antes de que salga de tu máquina.
Sacarlo del historial #
Para reescribir uso git filter-repo, que reemplazó a filter-branch. Cambia el hash de cada commit desde el que tocó el archivo, así que cualquiera que trabaje en el repo va a tener que volver a clonar.
Dos cosas te van a frenar la primera vez. Sobre un repo en el que venís trabajando, filter-repo no arranca:
Aborting: Refusing to destructively overwrite repo history since
this does not look like a fresh clone.Está pensado para correr sobre un clone recién hecho. Si lo hacés sobre el tuyo, va --force. Y cuando termina te borra el remoto origin a propósito, para que no pushees de memoria; si no lo volvés a agregar, el push falla con No configured push destination.
# primero: ¿en qué commits está?
git log --all --full-history --oneline -- .env
# después de rotar la key, recién ahí
git filter-repo --invert-paths --path .env --force
git remote add origin git@github.com:vos/tu-repo.git
git push --force --allLos hashes cambian todos, incluso los de commits anteriores al que tocó el archivo.
Que no vuelva a pasar #
Lo más barato es que el commit no salga. Un hook de pre-commit con gitleaks mira lo que estás por commitear y frena si encuentra algo con forma de secreto.
# frena el commit si encuentra algo con forma de secreto
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaksNo hace falta pasarle --redact: el hook ya corre gitleaks git --pre-commit --redact --staged, así que lo que encuentra sale tapado y no te deja el secreto en la salida de la terminal.
Y agregá .env al .gitignore antes del primer commit, con un .env.example sin valores para que se entienda qué variables hacen falta.
El criterio #
Si una key tocó un repo público, aunque sea un minuto, tratala como comprometida. No hay forma de probar que nadie la leyó, y rotarla cuesta menos que averiguarlo.
Lo próximo llega también por mail. Cada dos semanas, en Apuntes.
Comentarios
Pasan por moderación antes de aparecer.