simon mersich

Todavía nos pasamos archivos .env por chat

Así se comparten los secretos en la mayoría de los proyectos que vi, incluidos los míos: alguien comprime su .env, lo manda por privado, y la otra persona lo pega en su editor. Da igual si es Slack, WhatsApp, Discord o mail. El archivo termina en un historial de chat que queda respaldado para siempre, en un servidor que no controla ninguno de los dos.

Todo lo que viene después también está roto. La copia queda desactualizada apenas alguien agrega una clave, y te enterás cuando la app explota. Nadie sabe quién tiene qué versión. Cuando alguien deja el proyecto no hay forma de revocarle el acceso. Todos aceptan que todavía tiene la URL de la base de datos de producción en su carpeta de descargas. Y el .env.example que todos commiteamos lista las claves pero ninguno de los valores, así que la primera tarea del que llega es buscarlos en el historial de Slack.

Este problema está resuelto, y eso es lo que me molesta. Las soluciones existen, pero cada una te pide resignar algo que a un proyecto chico le cuesta más que el problema.

La respuesta SaaS es buena

Doppler, Infisical y 1Password llegaron al mismo modelo, y es el correcto. Los secretos nunca pasan por el disco. Prefijás tu comando y la herramienta inyecta los valores directo en el proceso:

doppler run -- npm start

Tu app ve las variables de entorno, y no hay ningún archivo que se pueda filtrar, quedar viejo o commitearse sin querer. Rotás un valor en el panel y la próxima ejecución lo toma. Tenés registro de auditoría, revocación real y versionado. En seguridad, esto le gana a cualquier cosa basada en archivos, incluida la que hice yo.

El costo es todo lo que lo rodea. Necesitás una cuenta, y el resto del proyecto también. Tus secretos viven en los servidores de otro, lo cual está bien hasta que es una decisión que tenés que defender. Y se paga por asiento. El plan de equipo de Doppler ronda los USD 7 por usuario por mes, y el plan pago de Infisical cuesta más. Para una empresa es un error de redondeo. Para dos estudiantes con un proyecto paralelo, es una factura mensual que alguien tiene que aceptar pagar.

Pero mi objeción real es el envoltorio en sí, y esto es más una preferencia que un argumento. Un proceso delante de cada comando tiene un costo en cada ejecución. Cada ejecución hace una ida y vuelta por red antes de que arranque tu app, así que npm run dev se vuelve más lento y bootear sin conexión deja de funcionar. También hay un proceso extra en el árbol, que importa más de lo que uno cree cuando sumás contenedores y manejo de señales.

El envoltorio también rompe las herramientas que leen el archivo en vez del entorno, y son un montón. El env_file: de Docker Compose quiere una ruta. Prisma busca .env. Tu editor lanza su propio explorador de bases de datos y su cliente REST por fuera del envoltorio, así que no ven nada. Cada una de esas cosas tiene un rodeo, y cada rodeo es una cosa más para configurar.

El deploy es la parte más molesta. Tu imagen de producción ahora necesita la CLI, un service token y salida a red hacia el proveedor al arrancar. Eso es una dependencia de runtime nueva y una forma nueva de que un deploy falle a las 2 de la mañana. Mientras tanto Vercel, Railway y Fly ya tienen sus propias interfaces de variables de entorno, así que terminás manteniendo dos sistemas y reconciliándolos a mano.

Debajo de todo eso, me gusta tener un archivo .env. Le puedo hacer cat y saber exactamente qué ve mi app. Funciona en un avión. Todos los frameworks de todos los lenguajes ya lo leen, sin adaptador y sin envoltorio. Pocas interfaces son así de universales, y no quería que una herramienta de secretos me obligara a dejarla.

La respuesta git-native encaja mejor

La otra familia, que incluye SOPS, git-crypt y dotenvx, guarda los secretos en el repo, encriptados. Este modelo me gusta bastante más para proyectos chicos. Los secretos quedan versionados junto al código que los necesita, llegan con git clone, el code review te muestra cuándo cambió un valor, y no hay ningún servicio que se pueda caer.

Intenté adoptar SOPS y no pude. Es una buena herramienta con usuarios reales, pero el camino estándar pasa por GPG, y nunca logré que GPG me funcionara. Perdí una tarde entre generación de claves, confianza, agentes y expiración, y abandoné. Los backends de KMS en la nube están bien si ya estás en AWS, pero eso significa que solo le sirven a quien ya tiene resuelto el manejo de claves.

dotenvx está más cerca de lo que quería, y viene de la persona que escribió dotenv. Mis reparos fueron prácticos. Nació en Node, y no me encanta necesitar un runtime de JavaScript dentro de un contenedor solo para leer un valor de configuración. Además, las funciones de equipo están detrás de un muro de pago.

Entonces, las herramientas SaaS quieren una cuenta, las opciones de KMS quieren una nube, y la alternativa git-native más amigable quiere un runtime. Ninguno de esos pedidos es irracional, pero cada uno era más grande que el problema que yo quería arreglar.

Lo que quería

Tenía una sola prueba, y creo que es la única que importa: la segunda persona clona el repo y corre la app. No debería tener que crearse una cuenta, esperar que la inviten a una organización, instalar un runtime, generar un par de claves e intercambiarlo conmigo. Clona, corre un comando y la app arranca.

Eso resulta ser, sobre todo, un problema de distribución de claves, y la distribución de claves es la parte que todos vuelven difícil. GitHub ya la resolvió, y pocas herramientas usan esa solución. GitHub publica las claves SSH públicas de cada usuario en github.com/<usuario>.keys. Si alguien puede pushear al repo, es casi seguro que ya tiene una clave ahí.

Esa es toda la idea detrás de hush:

hush add gh:alice

Eso busca las claves públicas de Alice, la agrega como destinataria, y commitea un .env.enc encriptado que vive en el repo como cualquier otro archivo. Ella clona, corre hush sync, y tiene un .env funcionando. Nadie tiene que crear una cuenta, mandar una invitación ni abrir el hilo de “¿me pasás tu clave pública?“. Es un único binario en Rust, así que no hay runtime que instalar. Está construido sobre age, así que la encriptación no la escribí yo. Para sacar a alguien, borrás una línea y pusheás.

Lo que no hace, a propósito

Me pasé varios párrafos hablando de las contrapartidas de los demás, así que acá va la mía. Después de hush sync, un .env en texto plano queda en tu disco, igual que hoy. Cualquier proceso de tu máquina puede leerlo, y cada vez más de esos procesos son agentes de código con acceso a shell. Las herramientas que envuelven el comando le ganan a hush en esto, y no voy a fingir que el archivo es seguro porque a mí me guste.

Lo que sí sostengo es que es la misma exposición que ya tenés, con el problema de compartir resuelto. Tu .env está en disco ahora mismo. La diferencia es de dónde salió esa copia, si está actualizada, y si se la podés sacar a alguien. Si la exposición en disco es tu modelo de amenaza, usá Doppler. Lo digo en serio.

Un comando exec que mantenga los valores fuera del disco está en el roadmap para quien lo quiera y para CI. Va a seguir siendo opcional, porque el archivo .env es justamente lo que quería conservar.

Es nueva, así que aplica el descargo del README: que alguien más la revise antes de confiarle secretos de producción. Si querés ser esa persona, el código está en github.com/mercho40/hush.

enes