6 puntos por GN⁺ 2025-10-15 | 2 comentarios | Compartir por WhatsApp
  • Aunque el software ha avanzado rápidamente, el sistema de variables de entorno del sistema operativo aún conserva una estructura de hace décadas
  • Las variables de entorno tienen la forma de un diccionario global de cadenas, una estructura simple sin namespace ni tipos
  • En Linux, las variables de entorno se transmiten del proceso padre al hijo mediante la llamada al sistema execve
  • Bash, glibc y Python administran las variables de entorno respectivamente como hashmaps, arreglos y envolturas de diccionario
  • El estándar POSIX no exige solo mayúsculas en los nombres y, de hecho, tiene reglas flexibles, como recomendar el uso de nombres en minúsculas

Qué son las variables de entorno

  • Aunque los lenguajes de programación han evolucionado rápidamente, la estructura base para ejecutar procesos que ofrece el sistema operativo, especialmente la parte de las variables de entorno, casi no ha cambiado
  • Al ejecutar una aplicación, si se quiere pasar parámetros de runtime sin archivos separados ni IPC, en la práctica no queda más que usar una interfaz basada en variables de entorno
  • Las variables de entorno funcionan como un diccionario plano de cadenas, sin namespace ni tipos

Estructura de creación y transmisión de variables de entorno

  • Las variables de entorno son un método tradicional para pasar valores entre procesos, y se envían junto con la ejecución de un proceso hijo por parte del proceso padre
    • Es decir, se heredan del proceso padre al hijo
  • En Linux, la syscall execve recibe como argumentos el ejecutable, los parámetros y el arreglo de variables de entorno (envp)
    • Ejemplo del comando de ejecución: ls -lah
      • filename: /usr/bin/ls
      • argv: ['ls', '-lah']
      • envp: ['PATH=...','USER=...']
  • El proceso padre puede pasarle al hijo el entorno existente tal cual o construir un entorno completamente nuevo
    • Casi todas las herramientas (Bash, subprocess.run de Python, la biblioteca de C execl, etc.) pasan las variables de entorno tal como están
    • Como excepción, algunas herramientas como login construyen un entorno nuevo

Dónde se almacenan y cómo se procesan internamente

  • Al iniciar un programa, el kernel guarda las variables de entorno en la pila en forma de cadenas terminadas en null
  • Estos datos son difíciles de modificar directamente desde el programa, por lo que normalmente se copian y se administran con una estructura propia dentro del proceso
  • Cómo almacenan las variables de entorno distintos lenguajes y shells
    • Bash: las administra como un hashmap (diccionario) con estructura de pila
      • En cada llamada de función se crea un mapa de scope local
      • Solo las variables con export se transmiten al proceso hijo
      • Incluso las variables declaradas con local pueden pasarse al proceso hijo mediante export
        • Ejemplo: con export PATH, un cambio local se refleja en el hijo, pero no afecta al entorno global
    • glibc (biblioteca de C): administra environ con putenv y getenv como una estructura de arreglo dinámico
      • Al ser un arreglo, tanto las consultas como los cambios tienen complejidad temporal lineal
      • Por eso no es adecuado para almacenar datos con altas exigencias de rendimiento
    • Python: internamente lo expone como un diccionario a través de os.environ, pero en realidad está vinculado al arreglo environ de la biblioteca de C
      • Cuando cambia un valor en os.environ, se llama a os.putenv y también se refleja en la biblioteca de C
      • En sentido contrario no se sincroniza, así que existe unidireccionalidad

Formato y rangos permitidos de las variables de entorno

  • El kernel de Linux y glibc son muy tolerantes con el formato de las variables de entorno
    • Puede haber varios valores duplicando el mismo nombre
    • Incluso se pueden registrar sin = y no hay restricciones sobre caracteres especiales como emojis
  • Límites de tamaño disponibles
    • Variable individual: 128 KiB (normalmente en entornos x64)
    • Suma total: 2 MiB (compartida con los argumentos de línea de comandos)
    • Las variables de entorno están limitadas a no superar 1/4 del espacio de pila

Particularidades y casos límite

  • Bash, en el caso de variables de entorno extrañas (duplicadas, entradas sin =, etc.), elimina nombres duplicados e ignora entradas anómalas
  • Si el nombre de una variable contiene espacios, Bash no puede referenciarlo, pero aun así puede transmitirlo al proceso hijo
    • Por ejemplo, Nushell y Python pueden crear variables con espacios en el nombre
    • Bash administra estas entradas en un hashmap separado (invalid_env)

Formato estándar y reglas de nombres

  • El estándar POSIX considera variable cualquier nombre que no contenga signo igual (=)
    • Recomendación oficial: el nombre debe usar solo mayúsculas, números y guiones bajos (y no puede empezar con un número)
    • Las variables en minúsculas están pensadas como namespace exclusivo de aplicaciones
    • Las herramientas estándar usan solo mayúsculas, pero también se permite usar variables en minúsculas
  • En la práctica, los desarrolladores suelen nombrarlas en estilo ALL_UPPERCASE
  • Regla recomendada: usar la expresión regular ^[A-Z_][A-Z0-9_]*$ para el nombre y UTF-8 para el valor
    • Si preocupan las excepciones o la compatibilidad, se recomienda usar el Portable Character Set (ASCII) de POSIX

Conclusión

  • Las variables de entorno siguen siendo una interfaz vieja pero indispensable, que actúa como frontera entre el sistema operativo y las aplicaciones
  • A pesar de sus limitaciones estructurales, Bash, C y Python, entre otros, las siguen usando envueltas de distintas maneras
  • En sistemas modernos, cada vez se necesita más una gestión de configuración con namespaces claros y un sistema de tipos

2 comentarios

 
howudoin 2025-10-15

Aunque parecía perder importancia a simple vista, con la llegada de Docker y la nube volvió a ser algo imposible de evitar.

 
GN⁺ 2025-10-15
Opiniones en Hacker News
  • Trabajo como SRE/sysadmin/DevOps/lo que sea; en el blog solo se hablaba de forma ligera sobre la estandarización de variables de entorno, pero quiero señalar que las alternativas también suelen ser igual de frustrantes, especialmente cuando hay secretos de por medio
    Cuando una aplicación está diseñada para acceder a un almacén de secretos específico como Hashicorp Vault/OpenBao/Secrets Manager, rápidamente termina en una fuerte dependencia del proveedor, y reemplazarlo se vuelve muy difícil porque el impacto llega hasta el nivel de las librerías
    La disponibilidad de Vault pasa a ser críticamente importante, y cuando hay que hacer upgrades o mantenimiento, el equipo de operaciones queda en una situación muy complicada
    Si pasas secretos mediante archivos de configuración, entonces surge el problema de cómo contener esos secretos, porque los archivos de config suelen estar en rutas públicas
    Al final terminas dependiendo de una de dos cosas: “reemplazo por plantillas antes de que el sistema privilegiado se lo entregue a la app” o “guardar el archivo de config completo en el almacén de secretos y entregárselo a la app”
    El trabajo con plantillas es propenso a errores, y mover el archivo de config completo al almacén de secretos también genera estrés porque siempre existe el riesgo de que alguien suba algo incorrecto
    Hoy en día la mayoría de los sistemas corren sobre contenedores, y salvo que sea una empresa con una infraestructura muy bien cuidada, los archivos de config siempre terminan en lugares raros, lo que hace que el proceso de montarlos sea todavía más confuso y propenso a errores
    Da igual si usas JSON/YAML/TOML o cualquier otro formato: los bugs peculiares son cosa de todos los días; por ejemplo, está el problema de Norway en YAML
    He visto sistemas que reciben secretos mediante la Kubernetes Secrets API, pero eso también se topa con el problema de una fuerte dependencia del proveedor
    A menos que estés diseñando algo específicamente como un operador, no recomendaría activamente ese enfoque
    También he visto problemas derivados de configurar variables de entorno a través de subprocess, pero siento que hoy los equipos prefieren sistemas basados en buses de mensajes, porque son más robustos y permiten escalar de forma independiente

    • En nuestro equipo tuvimos la experiencia de crear una librería ligera y genérica para secrets, y solo enchufábamos backends específicos del proveedor, como AWS Secrets Manager, por medio de plugins
      Permitía configurar caché local y opciones para omitir la caché por parámetro, así que toda la lógica realmente dependiente del proveedor quedaba confinada al backend, y la librería y la aplicación podían mantenerse sin dependencia del proveedor
      Incluso al migrar a Vault, solo agregamos un backend y cambiamos la configuración, y funcionó sin mayores problemas

    • Me da curiosidad por qué la Kubernetes secret API se percibe como un problema de dependencia del proveedor
      ¿Será que intentaban usar deployment yaml para algo distinto a un despliegue en Kubernetes?
      En la mayoría de las apps, si montas el secret dentro del contenedor y luego lo inyectas a la app como variable de entorno o como archivo json, puedes leerlo y usarlo de forma independiente del entorno
      Según entiendo, el cifrado del backend etcd también puede configurarse con KMS

    • No termino de entender por qué recibir secretos mediante la Kubernetes Secrets API sería lock-in
      En esencia, los K8s secrets no se almacenan cifrados por defecto, así que para que realmente tenga sentido, necesitas (0) usar K8s, (1) haber configurado cifrado en el control plane y (2) usar obligatoriamente una solución adicional como un driver CSI
      Y además, Secret Store CSI Driver soporta múltiples backends como Conjur, así que más bien va en dirección contraria al lock-in

    • Por todo esto, nosotros seguimos usando config principalmente con env vars y dotenv
      La estructura de configuración basada en variables de entorno es demasiado simple y además funciona bien con herramientas diversas, incluidos gestores de secretos
      En los últimos años también me ha empezado a interesar poco a poco sops basado en YAML
      YAML es realmente intuitivo para expresar la estructura de configuración de una app, y con sops es fácil cifrar y administrar solo partes específicas
      Eso sí, manejar claves GPG puede ser complicado, aunque se puede resolver con algo como Vault u OpenBao
      Pero claro, en ese proceso vuelve a aparecer el problema del lock-in del proveedor, aunque OpenBao parece un poco menos problemático en ese sentido

    • También puedes recibir variables de entorno como resultado de ejecutar un comando, así que es posible manejar esto sin proceso de plantillas y sin lock-in del proveedor

  • Un dato curioso más: setenv() está fundamentalmente roto en POSIX, así que creo que nunca debería usarse en código de librerías
    Incluso cuando tengas que usarlo en código de aplicación, debería ser el último recurso, y solo antes de crear hilos
    getenv() devuelve directamente un puntero al entorno original, así que cuando setenv() sobrescribe una variable, no hay ninguna protección
    Hay que tener muchísimo cuidado

    • Creo que la forma correcta de establecer variables de entorno es usando execve()
      Este enfoque solo es adecuado cuando transmites información por variables de entorno justo antes o después de exec()

    • No entiendo por qué alguien querría usar setenv dentro de código de librería

    • Solaris resolvió este problema, pero Linux sigue aferrado al mismo enfoque

    • NetBSD ha tenido desde hace mucho una alternativa segura llamada getenv_r(), y recientemente FreeBSD también la adoptó
      Probablemente macOS la siga pronto
      Ya hubo intentos de meterla en glibc o POSIX, pero fueron rechazados
      Espero que cuando se difunda más entre plataformas, algún día termine siendo aceptada oficialmente
      Documentación de getenv_r en NetBSD
      Commit de FreeBSD

  • Las variables de entorno se usan con frecuencia para pasar secretos, pero no me parece una buena práctica
    En Linux, todos los procesos que corren bajo el mismo usuario pueden mirar las variables de entorno entre sí
    Sea cual sea tu modelo de amenazas, esto preocupa especialmente en las máquinas de los desarrolladores, donde suele haber muchísimos procesos corriendo con el mismo usuario
    Este problema se vuelve más grave cuando hay muchos procesos moviéndose fuera del contenedor, como con agentes LLM
    Además, las variables de entorno normalmente se heredan tal cual a los procesos hijo, así que incluso si solo un proceso necesita el secreto, este tiende a quedar expuesto innecesariamente
    systemd muestra las variables de entorno a todos los clientes del sistema mediante DBUS, y en la documentación oficial también se advierte que no se deben guardar secretos en variables de entorno
    Si eso es cierto, significaría que variables de entorno configuradas en unidades solo para root podrían ser visibles incluso para usuarios normales, lo cual sería bastante impactante para muchos administradores de sistemas
    Al final, creo que la única solución que realmente evita la exposición en variables de entorno y archivos en texto plano es una estructura en la que el gestor de secretos entregue el secret mediante archivos temporales compartidos (por ejemplo, 1Password op cli, flask, terraform, etc.)
    El sistema de credentials de systemd funciona así. Pero todavía tiene poco soporte
    Si alguien conoce una buena forma de pasar secretos sin usar variables de entorno ni archivos en texto plano, me gustaría que la compartiera
    En el caso del cliente op de 1Password, siento que es seguro para usar en sesiones CLI porque cada sesión requiere mi aprobación, así que incluso si algún proceso malicioso invocara el binario op, igual necesitaría una aprobación aparte
    Ahora el problema que queda es cómo pasar ese secreto al proceso que realmente lo necesita, y siento que eso nos regresa al punto de partida
    Enlace de referencia a la documentación oficial de systemd sobre variables de entorno

    • Desde alrededor de 2012, las variables de entorno son tan seguras como la memoria normal
      Registro del commit relacionado
      Para leer las variables de entorno de otro proceso se requiere obligatoriamente permiso de ptrace, y si ya puedes leer por ptrace, en realidad también puedes leer todos los secretos, así que preocuparse por esto no aporta mucho
      La información de línea de comandos (cmdline) es otra historia, pero las variables de entorno ya no quedan expuestas tan fácilmente por esa vía

    • En el modelo de seguridad de la mayoría de los sistemas operativos, ejecutar algo bajo un mismo usuario equivale a entregarle por completo todos los privilegios de ese usuario
      Hay excepciones en forma de funciones de seguridad adicionales como capsicum en FreeBSD, landlock en Linux, SELinux, AppArmor o las integrity labels de Windows, pero la mayoría tienen limitaciones claras
      Al final, si es mi proceso, puedo matarlo, pausarlo o depurarlo libremente, y mediante ptrace/process_vm_readv/ReadProcessMemory, etc., siempre podré acceder a los secretos de un proceso que me pertenece
      Existen modelos de seguridad completamente distintos (sistemas operativos perfectamente basados en capabilities), pero la gran mayoría sigue este modelo, así que hay que entender sus límites y responsabilidades

    • Una buena forma de pasar secretos sin usar variables de entorno ni archivos en texto plano podría ser memfd_secret
      Página man de memfd_secret
      No hay mucho soporte por lenguaje o framework, así que valdría la pena probarlo vía FFI, especialmente en Rust o quizá también en Go
      En algún momento pensé en envolverlo directamente para PHP, pero no quería llegar al punto de modificar php-fpm, así que lo dejé
      En la práctica, lo más seguro sería que el process manager abriera por adelantado el file descriptor del secret y luego se lo pasara al proceso hijo, para poder usarlo sin exponerlo en memoria ni en otros lados

    • El modelo clásico de seguridad de Unix sigue usándose ampliamente, aunque con pequeñas mejoras, pero sus límites son muy evidentes en entornos baratos de cómputo o en entornos modernos
      Si necesitas ocultar secretos de otros procesos, la forma correcta desde el inicio es separarlos y ejecutarlos bajo otro usuario
      O bien usar acceso remoto desde el principio, aunque eso también trae desventajas y complejidad

    • Hoy en día, en plataformas de contenedores suele recomendarse pasar config o secretos mediante variables de entorno
      Dentro del contenedor, está diseñado para que otros procesos no puedan inspeccionar las variables de entorno
      Que las variables de entorno se hereden a los procesos hijo también es parte intencional del diseño, porque quien configura el entorno y posee el valor del secret es quien establece directamente ese entorno
      No veo como un gran problema la mayoría de los puntos de preocupación mencionados, aunque si hace falta podría discutirlos de forma más concreta

  • Muchos comentarios se enfocan en la gestión de secretos y sus problemas, pero también vale la pena pensar un momento en las ventajas de las variables de entorno
    Las variables de entorno son un “binding dinámico y extensible de variables con alcance indefinido” que conecta estructuralmente a los procesos Unix
    Más que compararlas de inmediato con un archivo de texto simple, conviene recordar que su razón de existir está en transmitir contexto de forma segura a los procesos hijo
    Cuanto más compleja es la estructura de procesos —shells anidados, subprocesos de programas complejos, etc.— más se luce el papel de las variables de entorno

  • Quiero recomendar Varlock, que de verdad me parece muy útil
    Te permite definir con claridad qué variables de entorno requiere un proyecto, cuáles son obligatorias u opcionales, su tipo de dato e incluso de dónde obtenerlas, y es fácil de administrar
    Sitio oficial de Varlock

  • Por experiencia práctica, un ejemplo de lo complejas que pueden volverse las variables de entorno: en una empresa donde trabajé hace años, alguna vez intenté depurar de dónde se estaba configurando cierta variable ENV y fue un caos total
    Al principio pensé que se definía en .bashrc o en algún lugar sencillo, pero en realidad se configuraba a través de al menos 10 capas: nivel compañía, región, unidad de negocio, equipo, individuo, etc.
    Al final solo pude rastrear una por una dónde se establecía activando las flags de depuración de bash

    • No sé si otros lenguajes también lo soportan, pero Node.js añadió recientemente una flag de línea de comandos que permite rastrear con precisión el acceso y los cambios en variables de entorno
      Documentación de Node.js sobre --trace-env
      Como los valores pueden configurarse o modificarse mediante muchísimas APIs, imagino que debe ser muy útil para depuración compleja

    • Es uno de esos casos que te hacen pensar: “¿no bastaría con un solo namespace?”

  • Hace mucho tiempo dejé de usar variables de entorno
    Ahora pongo un archivo dmd.conf junto al compilador y hago que el compilador lo lea directamente

  • El problema más grave de las variables de entorno es su carácter implícito y opaco
    En el mundo *nix, la mayoría de las apps tienden a depender de variables de entorno
    Aunque haya soporte adicional para métodos de configuración explícitos y transparentes (archivos de configuración, servicios remotos, argumentos de línea de comandos), el soporte para variables de entorno sigue siendo la tradición de este ecosistema
    Al final, las variables de entorno también son un hash map global que se clona y amplía para los procesos hijo; en 1979 eso quizá era un diseño razonable, pero hoy muchas veces termina siendo veneno
    Por ejemplo, Kubernetes contamina por defecto el entorno del contenedor con variables de entorno de “service link”
    Si las variables que espera la app chocan con las env vars por defecto, depurarlo se vuelve extremadamente difícil
    Referencia a la documentación oficial de Kubernetes
    Además de eso, siento que hay muchísimas prácticas que mantienen sin cuestionar marcos viejos como /bin, /usr/bin, /lib, /usr/lib
    Referencia: Q&A de Ubuntu sobre mantener directorios legacy

    • Combinaciones de teclas como hjkl también podrían verse como un ejemplo representativo de este tipo de tradición anticuada
      hjkl en vi viene de una terminal tonta de hace 40 años, y además era una terminal de pocas ventas
      (incluso menos que la Nokia N9)
  • Cada vez que configuro variables de entorno en Linux me entra una sensación de inseguridad
    La manera “oficial” de hacer que funcionen varía un poco entre distribuciones, y aunque sigas guías de internet, todo desaparece al reiniciar o al cerrar la terminal
    Ojalá existiera un editor GUI simple para variables de entorno globales, como en Windows
    Windows tiene la molestia de que hay que abrir una terminal nueva para que se apliquen los cambios, pero fuera de eso siempre funciona bien

    • Las variables de entorno, por definición, no persisten cuando cambia la sesión, así que lo normal es escribirlas en algún lugar que se ejecute de nuevo en cada sesión (login/terminal, etc.)
      Al iniciar sesión se ejecuta .bash_profile, y en las sesiones hijas se ejecuta .bashrc
      Si haces source de .bashrc desde .bash_profile y dejas la mayor parte de la configuración en .bashrc, es más fácil administrarlo
      Si no usas Bash sino otro shell como zsh o fish, entonces debes ajustarlo según ese shell
      En Linux no existe una GUI oficial y unificada para variables de entorno que aplique a todas las terminales
      Se podría crear una GUI que hiciera parsing complejo, pero en la práctica suele ser más fácil editarlo con un editor de texto

    • Desde mi perspectiva, que uso Linux principalmente, el comportamiento de Windows me resulta todavía más incómodo
      Demasiadas apps contaminan las variables de entorno, así que cuando algo falla, al final descubres que $SOFTWARE se estaba ejecutando desde una carpeta extraña o algo parecido, y eso genera mucha confusión

    • Si usas systemd, también es posible escribir KEY=VALUE en /etc/environment o /etc/environment.d/
      De hecho, parece que se podría hacer una GUI para eso
      Pero las variables de entorno no pueden inyectarse en procesos ya en ejecución; tienen la limitación de que solo se aplican tras reiniciar el proceso
      Referencia a la documentación oficial de systemd

    • Cómic Standards de xkcd
      Muestra de forma divertida que en Linux ya hay como 14 formas compitiendo para configurar variables de entorno, así que si alguien dice “unifiquémoslas en una sola”, al día siguiente habrá 15 estándares

  • Mi dato curioso favorito sobre variables de entorno es que cosas como PS1, que todo el mundo da por hecho que son variables de entorno, en realidad no lo son, sino variables del shell
    Ni siquiera puedes ver PS1 con el comando env