Cómo se filtran las claves de tu proyecto

Leonardo Gurgitano4 minLeer en inglés

Fachada cerrada de un edificio alto, con una sola abertura visible.
Fachada cerrada de un edificio alto, con una sola abertura visible.

Nadie sube una clave de API a propósito. Y sin embargo pasa todo el tiempo, en proyectos de gente que sabe lo que hace.

Pasa porque los caminos por los que se filtra no parecen peligrosos en el momento. Son cuatro, vale la pena conocerlos, y el primero es el que más sorprende.

1. Borrarla en el commit siguiente no la borra

Este es el que hay que entender antes que ningún otro.

Subiste sin querer un archivo con una clave. Te das cuenta a los cinco minutos, la borrás, hacés commit y push.

La clave sigue estando. Git guarda todo el historial: el commit donde la agregaste sigue ahí, y cualquiera que clone el repositorio puede verla con un comando:

git log -p --all -S "sk_live_"

Eso busca en todo el historial cualquier commit donde haya aparecido ese texto, aunque haya sido borrado después.

Y en un repositorio público hay programas dedicados a hacer exactamente esa búsqueda, todo el tiempo, sobre todo lo que se publica. El tiempo entre subir una clave y que alguien la use se mide en minutos.

Qué hacer si te pasa: limpiar el historial es posible, pero es lo segundo. Lo primero, siempre, es invalidar la clave y generar una nueva. Una vez que estuvo publicada, hay que asumir que alguien la tiene, y todo el esfuerzo de reescribir el historial no cambia eso.

2. El archivo que no estaba en .gitignore

El caso clásico. Tenés tu .env bien ignorado, pero:

.env            ← ignorado
.env.local      ← ignorado
.env.production ← ¡NO ignorado!

O creaste configuracion.local.json para probar algo y quedó fuera de la lista.

La regla que evita el problema es invertir la lógica: ignorar todo lo que empiece con .env y hacer una excepción explícita con el ejemplo.

# Ignorar todas las variantes
.env*

# Menos el archivo de ejemplo, que sí queremos versionar
!.env.example

Y antes de cada commit, mirá qué estás subiendo:

git status

Suena obvio. Es la comprobación que más filtraciones evita, y la que más se saltea cuando uno hace git add . sin mirar.

3. Los registros y los mensajes de error

Este no está en el repositorio, así que no lo detecta ninguna herramienta que mire el código.

// Se ve inofensivo
console.log('Llamando a la API', { url, headers });

Si en headers va el token de autorización, acabás de escribirlo en los registros. Y los registros suelen guardarse meses, viajar a un servicio externo y ser visibles para más gente que la base de datos.

Lo mismo con los errores. Muchas bibliotecas incluyen la petición completa en el mensaje cuando algo falla, con sus cabeceras adentro.

La costumbre que lo evita: nunca registres un objeto entero que pueda tener credenciales. Elegí los campos:

console.log('Llamando a la API', { url, metodo: 'POST' });

4. El código que le pasás a una herramienta de IA

Este es nuevo y todavía no está en la cabeza de casi nadie.

Cuando pegás un archivo de configuración en un chat para pedir ayuda, o cuando un asistente de código lee tu proyecto, las claves que haya en esos archivos salen de tu máquina.

No es un argumento para no usar esas herramientas. Es un motivo más para que las claves nunca estén escritas en el código: si viven en variables de entorno, el archivo que compartís dice process.env.API_KEY y no el valor.

Cómo saber si ya tenés una filtrada

Buscá en todo el historial los prefijos más comunes:

git log -p --all -S "sk_live_"     # Stripe
git log -p --all -S "AKIA"         # AWS
git log -p --all -S "ghp_"         # GitHub
git log -p --all -S "BEGIN RSA"    # claves privadas

Y para una revisión completa hay herramientas dedicadas que escanean todo el historial de una vez, como gitleaks o trufflehog.

La red que ya tenés puesta

Una buena noticia: GitHub tiene protección al subir activada por omisión en todos los repositorios públicos, incluidos los gratuitos. Si intentás subir algo que reconoce como una credencial, rechaza el push y te dice exactamente qué encontró.

Lo detecta para decenas de servicios: claves de AWS, tokens de GitHub, Stripe, Databricks, Shopify y muchos más, y la lista crece cada mes.

Con dos límites que conviene tener claros:

Solo reconoce formatos identificables. Un token de Stripe empieza con sk_live_ y se detecta. La contraseña de tu base de datos es una cadena cualquiera, y no hay forma de distinguirla de un texto normal.

No revisa el historial que ya subiste. Protege de aquí en adelante. Lo que ya está, sigue estando.

Lo mínimo que yo dejaría configurado

Tres cosas, y ninguna lleva más de cinco minutos:

Uno. El .gitignore con .env* y la excepción del ejemplo.

Dos. Un .env.example versionado con los nombres de las variables y sin los valores. Sirve de documentación y hace evidente cuál es el archivo real que no va al repositorio.

# .env.example
DATABASE_URL=postgres://usuario:contraseña@localhost:5432/basedatos
STRIPE_SECRET_KEY=sk_test_...

Tres. Una revisión del historial ahora, una sola vez, con los comandos de arriba. Si aparece algo, ya sabés cuál es el primer paso: invalidar esa clave antes que ninguna otra cosa.

Comentarios