6 points par GN⁺ 2025-10-15 | 2 commentaires | Partager sur WhatsApp
  • Alors que les logiciels ont évolué à grande vitesse, le système de variables d’environnement des systèmes d’exploitation conserve encore une structure vieille de plusieurs décennies
  • Les variables d’environnement prennent la forme d’un dictionnaire global de chaînes, une structure simple sans espace de noms ni typage
  • Sous Linux, les variables d’environnement sont transmises du processus parent à l’enfant via l’appel système execve
  • Bash, glibc, Python, etc. gèrent chacun les variables d’environnement sous forme de table de hachage, tableau ou enveloppe de dictionnaire
  • La norme POSIX n’impose pas que les noms soient uniquement en majuscules et applique en pratique des règles souples, avec notamment l’usage de noms en minuscules recommandé dans certains cas

Que sont les variables d’environnement ?

  • Même si les langages de programmation ont évolué rapidement, l’infrastructure d’exécution des processus fournie par les systèmes d’exploitation — en particulier la partie variables d’environnement — a très peu changé
  • Lorsqu’il faut transmettre des paramètres d’exécution au lancement d’une application sans fichier dédié ni IPC, on se retrouve en pratique à devoir utiliser une interface fondée sur les variables d’environnement
  • Les variables d’environnement jouent le rôle d’un dictionnaire plat de chaînes, sans espace de noms ni types

Structure de création et de transmission des variables d’environnement

  • Les variables d’environnement sont une méthode traditionnelle de transmission de valeurs entre processus ; elles sont transmises lorsque le processus parent lance le processus enfant
    • Autrement dit, elles sont héritées du processus parent par le processus enfant
  • Sous Linux, l’appel système execve reçoit comme arguments le binaire à exécuter, les arguments, ainsi que le tableau des variables d’environnement (envp)
    • Exemple de commande exécutée : ls -lah
      • filename: /usr/bin/ls
      • argv: ['ls', '-lah']
      • envp: ['PATH=...','USER=...']
  • Le processus parent peut transmettre tel quel l’environnement existant au processus enfant, ou construire un environnement entièrement nouveau
    • Presque tous les outils (Bash, subprocess.run de Python, execl de la bibliothèque C, etc.) transmettent les variables d’environnement telles quelles
    • À l’inverse, certains outils comme login construisent un nouvel environnement

Emplacement de stockage et traitement interne des variables d’environnement

  • Au démarrage d’un programme, le noyau stocke les variables d’environnement sur la pile sous forme de chaînes null-terminated
  • Ces données sont difficiles à modifier directement par le programme ; elles sont donc généralement copiées et gérées dans une structure interne propre au programme
  • Méthode de stockage des variables d’environnement selon les langages et shells
    • Bash : gestion via une table de hachage (dictionnaire) de structure empilée
      • Création d’une map de portée locale à chaque appel de fonction
      • Seules les variables marquées export sont transmises au processus enfant
      • Les variables déclarées avec local peuvent aussi être transmises au processus enfant via export
        • Exemple : export PATH permet de répercuter une modification locale vers l’enfant sans affecter la portée globale
    • glibc (bibliothèque C) : gestion de environ, structure en tableau dynamique, via putenv et getenv
      • Avec une structure en tableau, la recherche comme la modification ont toutes deux une complexité temporelle linéaire
      • Ce n’est donc pas adapté à un usage comme stockage de données demandant de fortes performances
    • Python : exposées en interne via os.environ comme un dictionnaire, mais en réalité connectées au tableau environ de la bibliothèque C
      • Lorsqu’une valeur de os.environ est modifiée, os.putenv est appelé, ce qui répercute le changement dans la bibliothèque C
      • L’inverse n’est pas synchronisé, d’où une relation à sens unique

Format des variables d’environnement et plage autorisée

  • Le noyau Linux et glibc sont très permissifs sur le format des variables d’environnement
    • Il peut exister plusieurs valeurs en doublon pour un même nom
    • Un enregistrement sans = est également possible, et il n’y a pas de restriction sur les caractères spéciaux comme les emoji
  • Limites de taille disponibles
    • Variable individuelle : 128 KiB (en général sur un environnement x64)
    • Total cumulé : 2 MiB (partagé avec les arguments de ligne de commande)
    • Les variables d’environnement sont limitées à un quart de l’espace de pile

Particularités et cas limites des variables d’environnement

  • Bash, face à des variables d’environnement étranges (doublons, entrées sans =, etc.), supprime les noms dupliqués et ignore les entrées anormales
  • Si un nom de variable contient des espaces, Bash ne peut pas le référencer, mais il peut tout de même le transmettre au processus enfant
    • Par exemple, Nushell ou Python peuvent créer des variables dont le nom contient des espaces
    • Bash stocke ce type d’entrée dans une table de hachage distincte (invalid_env)

Règles standard de format et de nommage des variables d’environnement

  • La norme POSIX reconnaît comme variable tout nom ne contenant pas de signe égal (=)
    • Recommandation officielle : le nom ne devrait contenir que des majuscules, des chiffres et des underscores (sans chiffre en première position)
    • Les variables en minuscules sont destinées à un espace de noms réservé aux applications
    • Les outils standard n’utilisent que les majuscules, mais l’usage de variables en minuscules est également autorisé
  • En pratique, les développeurs utilisent surtout la convention ALL_UPPERCASE
  • Règle recommandée : utiliser pour les noms de variables l’expression régulière ^[A-Z_][A-Z0-9_]*$, et pour les valeurs l’UTF-8
    • En cas d’exception ou d’inquiétude de compatibilité, il est recommandé d’utiliser le Portable Character Set (ASCII) de POSIX

Conclusion

  • Les variables d’environnement restent une interface vieillissante mais indispensable, servant de frontière entre le système d’exploitation et les applications
  • Malgré leurs limites structurelles, Bash, C, Python, etc. continuent de les exploiter en les enveloppant chacun à leur manière
  • Dans les systèmes modernes, le besoin de modes de gestion de configuration plus explicites, avec espace de noms clair et système de types, se fait de plus en plus sentir

2 commentaires

 
howudoin 2025-10-15

Cela semblait perdre un peu de son importance à première vue, mais avec l'arrivée de Docker et du cloud, c'est redevenu inévitable.

 
GN⁺ 2025-10-15
Commentaires sur Hacker News
  • Je travaille comme SRE/sysadmin/DevOps/autre, et même si l’article de blog ne parlait que de façon assez simple de la standardisation des variables d’environnement, je voudrais souligner que les alternatives provoquent elles aussi une frustration comparable, surtout quand des secrets entrent en jeu
    Une architecture dans laquelle l’application accède à un coffre à secrets spécifique comme Hashicorp Vault/OpenBao/Secrets Manager entraîne vite un verrouillage fournisseur sérieux, et le remplacement devient très difficile jusque dans les bibliothèques
    La disponibilité de Vault devient alors critique, et les équipes d’exploitation se retrouvent dans une situation très délicate lorsqu’il faut faire une mise à niveau ou de la maintenance
    Quand on transmet les secrets via un fichier de config, on se retrouve aussi embarrassé quant à la manière de les y placer, car les fichiers de config se trouvent souvent sur des chemins publics
    Au final, on finit par dépendre soit d’un système « privilégié qui remplace les valeurs via un template avant de les transmettre à l’app », soit du fait de « stocker tout le fichier de config dans le coffre à secrets pour le transmettre à l’app »
    Le templating est propice aux erreurs, et déplacer l’intégralité du fichier de config vers un coffre à secrets est aussi source de stress, car quelqu’un peut l’uploader de travers
    Aujourd’hui, la plupart des systèmes tournent sur des conteneurs, et sauf dans les entreprises très rigoureuses sur l’infra, les fichiers de config se retrouvent toujours à des endroits improbables, ce qui rend le montage encore plus confus et multiplie les erreurs
    Quel que soit le format utilisé, JSON/YAML/TOML, les bugs particuliers sont monnaie courante, comme par exemple le problème Norway de YAML
    J’ai déjà vu des approches où les secrets sont fournis via l’API Kubernetes Secrets, mais là aussi on se heurte à un fort verrouillage fournisseur
    À moins de concevoir spécialement un système de type operator, je ne recommande pas activement cette méthode
    J’ai aussi vu des problèmes liés à la définition de variables d’environnement via des subprocess, mais j’ai l’impression qu’aujourd’hui les équipes préfèrent des systèmes fondés sur un bus de messages, plus robustes et capables de s’étendre indépendamment

    • Dans notre équipe, nous avions créé une petite bibliothèque générique de gestion des secrets, sur laquelle on branchait simplement en plugin des backends spécifiques à chaque fournisseur comme AWS Secrets Manager
      Avec un cache local configurable et des options pour contourner le cache selon les paramètres, toute la logique réellement dépendante du fournisseur restait cantonnée au backend, ce qui permettait de garder la bibliothèque et l’application indépendantes du fournisseur
      Lors du passage à Vault, il a suffi d’ajouter un backend et de changer la configuration, et tout s’est appliqué sans incident

    • Je me demande pourquoi l’API Kubernetes Secret est perçue comme un problème de verrouillage fournisseur
      Est-ce que c’était parce que vous vouliez utiliser les deployment yaml à autre chose qu’à un déploiement Kubernetes ?
      Pour la plupart des applications, on peut monter le secret dans le conteneur puis l’injecter dans l’application sous forme de variable d’environnement ou de fichier json, ce qui permet de le lire et de l’écrire indépendamment de l’environnement
      Il me semble aussi que le chiffrement du backend etcd peut être configuré avec KMS

    • J’ai du mal à comprendre en quoi recevoir des secrets via l’API Kubernetes Secrets constitue un verrouillage
      Fondamentalement, les secrets K8s ne sont pas stockés chiffrés, donc à mon avis cela n’a de sens que si l’on (0) utilise bien K8s, (1) a mis en place le chiffrement côté control plane, et (2) utilise obligatoirement une solution supplémentaire comme un pilote CSI
      Et le Secret Store CSI Driver prend en charge plusieurs backends, comme Conjur, donc c’est plutôt l’inverse d’un verrouillage

    • Pour ces raisons, nous continuons à utiliser surtout les env vars et dotenv pour la configuration
      Une structure de configuration basée sur les variables d’environnement est extrêmement simple et compatible avec divers outils, y compris les gestionnaires de secrets
      Ces dernières années, j’ai commencé à m’intéresser un peu à sOps basé sur YAML
      YAML est vraiment intuitif pour représenter la structure de configuration d’une application, et avec sops il est facile de chiffrer et gérer seulement certaines parties
      La gestion des clés GPG reste toutefois délicate, mais on peut la résoudre avec Vault ou OpenBao
      Cela dit, cela réintroduit encore une fois la question du verrouillage fournisseur, même si OpenBao semble un peu moins concerné

    • On peut aussi récupérer les variables d’environnement à partir du résultat d’une commande, ce qui permet de traiter ça sans verrouillage fournisseur et sans étape de templating

  • Autre fait intéressant : setenv() est fondamentalement cassé dans POSIX, au point que je pense qu’il ne faut jamais l’utiliser dans du code de bibliothèque
    Même dans le code applicatif, cela doit rester un dernier recours, et uniquement avant la création de threads
    getenv() renvoie directement le pointeur d’origine de la variable d’environnement, donc quand setenv() remplace une variable, il n’y a aucun mécanisme de protection
    Il faut être extrêmement prudent

    • À mon sens, la bonne façon de définir correctement les variables d’environnement est de les configurer via execve()
      Cette méthode n’est adaptée que lorsqu’on transmet des informations par variables d’environnement juste avant ou juste après exec()

    • Je ne comprends pas pourquoi on voudrait utiliser setenv dans du code de bibliothèque

    • Solaris a résolu ce problème, mais Linux persiste encore avec la même approche

    • NetBSD propose depuis longtemps une alternative sûre appelée getenv_r(), et FreeBSD l’a récemment adoptée
      macOS suivra probablement bientôt
      Il y a déjà eu des tentatives pour l’ajouter à glibc ou POSIX, mais elles ont été rejetées
      J’espère qu’une fois diffusé sur plusieurs plateformes, cela finira par être officiellement accepté
      Documentation NetBSD de getenv_r
      Commit FreeBSD

  • Les variables d’environnement sont souvent utilisées pour transmettre des secrets, mais je ne pense pas que ce soit une très bonne pratique
    Sous Linux, tous les processus exécutés par le même utilisateur peuvent inspecter les variables d’environnement les uns des autres
    Quel que soit le modèle de menace retenu, c’est préoccupant, surtout sur les machines des développeurs où énormément de processus tournent sous le même utilisateur
    Ce problème devient encore plus grave avec des éléments comme les agents LLM, quand de nombreux processus se promènent en dehors des conteneurs
    En outre, les variables d’environnement sont généralement héritées telles quelles par les processus enfants, ce qui tend à exposer sans discernement les secrets même lorsqu’un seul processus en a réellement besoin
    systemd expose les variables d’environnement à tous les clients système via DBUS, et sa documentation officielle avertit de ne pas y stocker de secrets
    Si c’est vrai, cela voudrait dire que des variables d’environnement définies dans une unité réservée à root pourraient être visibles par des utilisateurs ordinaires, ce qui serait assez choquant pour beaucoup d’administrateurs système
    Au final, je pense que la seule solution permettant d’échapper à l’exposition via les variables d’environnement et les fichiers en clair est une architecture où le gestionnaire de secrets transmet les secrets via un partage de fichier temporaire, comme avec le op cli de 1Password, flask ou terraform
    Le système de credentials de systemd suit cette logique. Mais il est encore peu pris en charge
    Si quelqu’un connaît une bonne méthode pour transmettre des secrets sans variables d’environnement ni fichiers en clair, j’aimerais bien la connaître
    À titre de référence, dans le cas du client op de 1Password, il faut mon approbation à chaque session, donc je trouve cela sûr dans une session CLI ; même si un processus malveillant appelait le binaire op, une approbation séparée serait exigée
    Le problème restant est alors de savoir comment transmettre ce secret au processus qui en a réellement besoin, et on a l’impression de revenir au point de départ
    Lien vers la documentation officielle systemd sur les variables d’environnement

    • Depuis environ 2012, les variables d’environnement sont devenues aussi sûres que la mémoire ordinaire
      Historique du commit correspondant
      Pour lire les variables d’environnement d’un autre processus, il faut impérativement avoir les droits ptrace, et si l’on peut déjà utiliser ptrace, on peut de toute façon lire tous les secrets, donc cette inquiétude n’aurait pas vraiment de sens
      Les informations de ligne de commande (cmdline) sont un autre sujet, mais les variables d’environnement ne s’exposent plus aussi facilement de cette manière

    • Dans le modèle de sécurité de la plupart des systèmes d’exploitation, exécuter sous un même utilisateur revient à accorder pleinement tous les droits de cet utilisateur
      Il existe quelques mécanismes supplémentaires comme capsicum sur FreeBSD, landlock, SELinux, AppArmor sous Linux, ou les integrity labels sous Windows, mais la plupart ont des limites bien marquées
      En pratique, je suis libre de tuer, suspendre ou déboguer mes propres processus, et je peux toujours accéder aux secrets d’un processus qui m’appartient via ptrace/process_vm_readv/ReadProcessMemory, etc.
      Il existe bien des modèles de sécurité complètement différents, comme des OS parfaitement fondés sur les capabilities, mais la grande majorité suit encore ce modèle, et il faut en connaître les limites et les responsabilités

    • memfd_secret me vient à l’esprit comme bonne méthode pour transmettre des secrets sans variables d’environnement ni fichiers en clair
      page man de memfd_secret
      Le support n’est pas très répandu selon les langages ou frameworks, donc cela pourrait valoir le coup d’essayer via FFI en Rust, ou en Go si c’est possible aussi
      J’avais envisagé de l’encapsuler directement côté PHP, mais j’ai abandonné car je ne voulais pas aller jusqu’à modifier php-fpm
      En pratique, le plus sûr serait qu’un gestionnaire de processus ouvre à l’avance un descripteur de fichier secret puis le transmette au processus enfant, afin qu’il puisse l’utiliser sans exposition en mémoire ou ailleurs

    • Le modèle de sécurité Unix classique reste encore très utilisé, avec quelques améliorations, mais il montre clairement ses limites dans les environnements récents ou à faible coût
      S’il faut cacher des secrets à d’autres processus, la bonne pratique consiste d’emblée à les exécuter sous des utilisateurs différents
      Sinon, il y a aussi l’approche consistant à y accéder à distance, mais elle entraîne elle aussi ses inconvénients et sa complexité

    • Aujourd’hui, sur les plateformes de conteneurs, il est plutôt recommandé de transmettre la config ou les secrets via des variables d’environnement
      Dans un conteneur, les autres processus sont conçus pour ne pas pouvoir inspecter les variables d’environnement
      Le fait que les variables d’environnement soient héritées par les processus enfants est intentionnel, car l’entité qui configure l’environnement avec la valeur du secret définit aussi directement cet environnement
      Je ne considère pas la plupart des problèmes évoqués comme particulièrement graves, mais je suis prêt à en discuter concrètement si besoin

  • Beaucoup de commentaires se concentrent sur la gestion des secrets et ses problèmes, mais cela vaut aussi la peine de réfléchir aux avantages des variables d’environnement
    Les variables d’environnement constituent une « liaison dynamique de variables à portée indéfinie » qui relie structurellement les processus Unix
    Plutôt que de les comparer simplement à des fichiers texte, il faut se rappeler que leur raison d’être est la transmission de contexte pour faire passer en toute sécurité des informations aux processus enfants
    Plus la structure des processus est complexe — shells imbriqués, sous-processus de programmes complexes, etc. — plus le rôle des variables d’environnement devient pertinent

  • Je recommande vraiment Varlock, c’est très utile
    Il permet de définir clairement les variables d’environnement nécessaires à un projet, si elles sont obligatoires ou facultatives, leur type, et même d’où elles doivent venir, ce qui facilite beaucoup leur gestion
    Site officiel de Varlock

  • D’après mon expérience en conditions réelles, pour illustrer à quel point les variables d’environnement peuvent devenir complexes, j’ai complètement perdu pied un jour en essayant de déboguer l’endroit où une certaine variable ENV était définie dans une ancienne entreprise
    Au départ, je pensais qu’elle était définie dans .bashrc ou un autre endroit simple du même genre, mais en réalité elle passait par au moins dix couches : niveau entreprise, région, division, équipe, individuel, etc.
    J’ai finalement dû activer les flags de debug de bash pour remonter laborieusement la trace et comprendre où elle était définie

    • Je ne sais pas si d’autres langages le prennent en charge, mais Node.js a récemment ajouté un flag en ligne de commande qui permet de tracer précisément l’accès et les modifications des variables d’environnement
      Documentation Node.js sur --trace-env
      Comme les valeurs peuvent être définies ou modifiées via d’innombrables API, cela me semble extrêmement utile pour les débogages complexes

    • C’est le genre de cas qui fait penser : « un seul espace de noms ne suffirait-il pas ? »

  • J’ai renoncé aux variables d’environnement il y a longtemps
    J’utilise maintenant un fichier dmd.conf à côté du compilateur, que celui-ci lit directement

  • Le problème le plus grave des variables d’environnement, c’est leur caractère implicite et opaque
    Dans le monde *nix, la plupart des applications ont tendance à dépendre des variables d’environnement
    Même lorsqu’une méthode de configuration explicite et transparente est aussi prise en charge — fichier de configuration, service distant, argument en ligne de commande — la prise en charge des variables d’environnement reste une tradition du milieu
    Au fond, les variables d’environnement sont aussi une table de hachage globale, clonée et étendue pour les processus enfants ; c’était peut-être une conception raisonnable en 1979, mais aujourd’hui cela devient souvent toxique
    Par exemple, Kubernetes pollue par défaut l’environnement des conteneurs avec des variables d’environnement de type « service link »
    Si les variables d’environnement attendues par l’application entrent en conflit avec ces variables par défaut, le débogage devient extrêmement difficile
    Référence à la documentation officielle de Kubernetes
    Au-delà de cela, j’ai l’impression qu’il y a énormément d’autres cas où l’on conserve sans esprit critique des cadres hérités, comme /bin, /usr/bin, /lib, /usr/lib
    Référence : Q&R Ubuntu sur le maintien des répertoires historiques

    • On peut voir des combinaisons de touches comme hjkl comme un autre exemple typique de ce conservatisme
      Dans vi, hjkl vient des terminaux rudimentaires d’il y a 40 ans, et ces terminaux se vendaient peu
      (encore moins qu’un Nokia N9)
  • Chaque fois que je définis une variable d’environnement sous Linux, un sentiment d’anxiété m’envahit
    La manière officiellement correcte de faire varie un peu selon les distributions, et même en suivant les guides en ligne, tout disparaît après un redémarrage ou quand on ferme le terminal
    J’aimerais qu’il existe un éditeur GUI simple de variables d’environnement globales comme sous Windows
    Sous Windows, il y a bien l’inconvénient de devoir rouvrir le terminal pour prendre en compte les changements, mais à part ça, cela fonctionne toujours bien

    • C’est normal que les variables d’environnement ne persistent pas d’une session à l’autre ; il faut donc les écrire à un endroit réexécuté à chaque session, comme au login ou à l’ouverture du terminal
      À la connexion, .bash_profile est exécuté, et pour les sessions enfants, c’est .bashrc
      Si l’on fait un source de .bashrc depuis .bash_profile et que l’on place la plupart des réglages dans .bashrc, cela devient plus facile à gérer
      Si l’on n’utilise pas Bash mais un autre shell comme zsh ou fish, il faut s’aligner sur le fonctionnement de ce shell
      Sous Linux, il n’existe pas de GUI officiel et unifié pour les variables d’environnement applicable à tous les terminaux
      On pourrait bien créer une GUI avec un parsing complexe, mais en pratique il est plus simple de modifier cela avec un éditeur de texte

    • Comme j’utilise principalement Linux, le comportement de Windows me paraît au contraire plus inconfortable
      Trop d’applications polluent les variables d’environnement, et quand quelque chose ne fonctionne pas, on finit souvent par découvrir que $SOFTWARE s’exécutait depuis un dossier étrange ou quelque chose du genre

    • Si l’on utilise systemd, on peut aussi écrire KEY=VALUE dans /etc/environment ou /etc/environment.d/
      On pourrait d’ailleurs sans doute créer une GUI pour cette méthode
      Cela dit, les variables d’environnement ne peuvent pas être injectées dans un processus déjà en cours d’exécution, donc il faut redémarrer pour appliquer les changements, ce qui reste une limite
      Référence à la documentation officielle de systemd

    • BD xkcd sur les standards
      Elle illustre avec humour le fait que Linux a déjà 14 méthodes concurrentes pour définir des variables d’environnement, et que si l’on propose de les unifier, on se retrouve le lendemain avec un 15e standard

  • Mon anecdote préférée sur les variables d’environnement, c’est que des choses comme PS1, que tout le monde considère naturellement comme des variables d’environnement, n’en sont en réalité pas ; ce sont des variables du shell
    On ne peut même pas voir PS1 avec la commande env