Todavía nos pasamos archivos .env por chat
Así es como 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. Slack, WhatsApp, Discord, mail — da igual cuál. 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 revocación: hay un acuerdo tácito de 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 obedientemente documenta la forma de la configuración sin decirte nada de los valores, así que la primera tarea del que llega es una búsqueda del tesoro por el historial de Slack.
Este problema está resuelto. Eso es lo que me molesta. Las soluciones existen — solo que cada una te pide resignar algo que, para un proyecto chico, cuesta más que el problema.
La respuesta SaaS es genuinamente buena
Doppler, Infisical y 1Password llegaron al mismo modelo, y es el correcto: no pongas los secretos en 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. No hay archivo que se filtre, ni que quede viejo, ni que se commitee 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 el eje de 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 pago de Infisical más. Para una empresa es un error de redondeo. Para dos estudiantes con un proyecto paralelo es una suscripción, un registro y una conversación.
Pero mi objeción real es el envoltorio en sí, y esto es más una preferencia que un argumento. Poner un proceso delante de cada comando tiene un costo que pagás todo el tiempo. 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. Hay un proceso extra en el árbol, que importa más de lo que uno cree cuando aparecen contenedores y manejo de señales.
También rompe cosas en silencio, porque un montón de herramientas leen el archivo, no el entorno. El env_file: de Docker Compose quiere una ruta. Prisma busca .env. El explorador de bases de datos y el cliente REST de tu editor los lanza tu editor, no el envoltorio, así que no ven nada. Cada una de esas cosas tiene un rodeo, y los rodeos son el impuesto.
El deploy es donde se pone realmente molesto. Ahora la CLI tiene que existir adentro de tu imagen de producción, con un service token, con salida a red hacia el proveedor al arrancar — 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.
Y debajo de todo eso: me gusta genuinamente 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 entienden, sin adaptador y sin envoltorio. Es una de las poquísimas interfaces verdaderamente universales que tenemos, y no quería que una herramienta de secretos fuera la razón por la que la resignaba.
La respuesta git-native tiene la forma correcta
La otra familia guarda los secretos en el repo, encriptados. SOPS, git-crypt, dotenvx. 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 pueda estar caído.
Intenté adoptar SOPS y reboté. Es una buena herramienta con usuarios reales, pero el camino estándar pasa por GPG, y nunca logré que el enfoque con GPG me funcionara. Generación de claves, confianza, agentes, expiración — perdí una tarde y abandoné. Los backends de KMS en la nube están bien si ya vivís en AWS, que es otra forma de decir que le resuelven el problema a quien ya lo tenía resuelto.
dotenvx está más cerca de lo que quería y viene de la persona que escribió dotenv, así que los instintos son buenos. 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. 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. Simplemente cada uno era más grande que la cosa que yo quería arreglar.
Lo que realmente quería
Una sola prueba, y creo que es la única que importa: la segunda persona clona el repo y corre la app. No “la segunda persona se crea una cuenta, la invitan a una organización, instala un runtime, genera un par de claves y me lo intercambia”. Clonar, un comando, andando.
Que 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. Salvo que GitHub ya la resolvió y nadie la usa: las claves SSH públicas de cualquier usuario de GitHub están servidas, públicamente, en github.com/<usuario>.keys. Si alguien puede pushear al repo, es casi seguro que ya tiene una clave ahí.
Así que esa es la idea completa 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. Sin cuenta, sin invitación, sin intercambio de claves, sin el hilo de “¿me pasás tu clave pública?“. Es un único binario en Rust, así que no hay runtime que instalar, y está construido sobre age, así que la encriptación no la escribí yo. Sacar a alguien es borrar una línea y pushear.
Lo que no hace, a propósito
Siendo honesto con mi propia contrapartida, ya que me pasé varios párrafos hablando de las de los demás: 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 — incluidos, cada vez más, los agentes de código con acceso a shell. Las herramientas que envuelven el comando me ganan ahí, 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 encima. 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 o para CI. Pero va a ser una opción, no el destino. El archivo no es una limitación que estoy rodeando: es justamente lo que quería conservar.
Es nueva, así que aplica el descargo del README: conseguí un segundo par de ojos antes de confiarle secretos de producción. Si querés ser ese segundo par de ojos, está en github.com/mercho40/hush.