4 · Seguridad·5 min de lectura
La llave que llevaba tres meses en un archivo
Escribimos un escáner para buscar credenciales sueltas en nuestro propio código. Encontró 19 llaves distintas repartidas en 105 copias, 90 de ellas fuera de un archivo de entorno.
Nuestras. No de un cliente.
Por qué un escáner y no una bóveda#
La tentación al descubrir esto es comprar un gestor de secretos. Pero el problema no era no tener dónde guardarlas: .env.local, los secretos de Cloudflare y las credenciales de n8n ya existían y funcionaban.
El problema era que nadie se enteraba cuando una llave salía de ahí. Una se pegó en una nota interna en junio y siguió ahí tres meses. Una bóveda no impide pegar. Un escáner que falla el despliegue, sí.
Lo que apareció, en orden de daño#
La llave de servicio de Supabase. Ignora las reglas de seguridad a nivel de fila: lee y borra todo — los datos de los dos clientes que viven en esa base. Estaba en el historial de Git, en nueve commits. El repositorio es privado, pero eso solo significa que el riesgo está acotado a quien tenga acceso, no que no exista.
La contraseña de fábrica de Firebird, masterkey, en 22 copias. Es la que trae la instalación por omisión del ERP. El puerto solo es alcanzable por la red privada, pero sigue siendo la de fábrica.
La llave de Groq escrita dentro de un nodo de n8n, no como credencial. Un nodo se exporta, se respalda y se comparte con la llave adentro.
Y otra de Supabase que el escáner no vio en la primera pasada, porque solo buscaba el formato viejo de esas llaves. Estaba en los encabezados de tres nodos HTTP.
La frase que hay que decir en voz alta#
Borrar el archivo no invalida la llave.
Lo que está en el historial de Git sigue ahí para cualquiera con acceso al repositorio. Reescribir el historial es opcional. Rotar no lo es.
Qué hicimos#
El escáner nunca imprime un valor. Reporta tipo, emisor, ubicación, una huella corta —sha256 de ocho caracteres, para poder decir "ésta es la misma que aquélla" sin exponerla— y una muestra enmascarada. Un reporte de seguridad que filtra secretos es el chiste que se cuenta solo.
Distingue por zona, no por patrón. Una llave en .env.local fuera de Git está en su lugar; la misma llave en una nota o en el código fuente, no. Y un respaldo de un .env cuenta como fuga: es una copia que sobrevive a la rotación del original y que nadie vuelve a mirar. Lo descubrimos porque nuestros propios scripts habían dejado cuatro respaldos con veinte llaves adentro.
Tiene autopruebas. Ocho casos que verifican que detecta lo que debe, que enmascara de verdad y que la huella es irreversible. Un escáner roto y uno que no encuentra nada se ven exactamente igual.
Y corre en cada despliegue. Con hallazgos en zonas que deben estar limpias —código, notas, documentación— falla el despliegue.
El orden de rotación no es por antigüedad, es por daño si se filtra: crear la nueva, actualizar cada destino, verificar que lo que la usa sigue vivo, revocar la vieja, y solo al final borrar el texto. Ese último paso es el menos importante: para entonces la llave ya no vale nada.
Lo que nos llevamos#
Todo el mundo tiene esto. La diferencia no está en no cometer el error —se comete— sino en cuánto tarda alguien en enterarse. Tres meses fue nuestra respuesta antes del escáner. Ahora es el próximo despliegue.
Y publicamos esto sabiendo cómo se ve. Un proveedor de seguridad contando que tenía 90 credenciales fuera de lugar suena mal. Suena peor no contarlo y que sea verdad igual.