世界を動かす技術を、日本語で。

環境変数はレガシーの混乱です:深く掘り下げてみましょう

2025年10月14日原文(allvpv.org)

概要

  • プログラミング言語 の進化と対照的に、 環境変数 の仕組みはUnix時代からほぼ変化なし
  • 環境変数は 親プロセスから子プロセスへ 引き継がれる仕組み
  • Linuxでは execveシステムコール を通じて環境変数が渡される
  • Bash、C、Python など言語ごとに内部管理方法が異なる
  • POSIX標準では 大文字推奨 だが、実際の制限は緩い

ソフトウェア開発における環境変数の基礎

  • 環境変数 は、アプリケーションの ランタイムパラメータ として利用される仕組み
  • ファイルやIPC、ネットワークを使わずに 値を受け渡す ための手段
  • グローバルかつフラットな文字列辞書 であり、 型や名前空間 は存在しない
  • 例: export SECRET_API_KEY=2u845102348u234 のように値を設定

環境変数の正体と伝播

  • 環境変数は OS内部の特別な辞書 ではなく、 親から子へ明示的に引き継がれるデータ
  • Linuxでは execveシステムコール が新プロセス起動時にenvp配列として渡す
    • 引数: filename(実行ファイルパス)、argv(コマンドライン引数)、envp(環境変数配列)
  • ほとんどのツール(Bash、Python subprocess、Cのexecl等)は 親の環境変数をそのまま子へ渡す
  • 例外: loginコマンド などは新しい環境をセットアップ

カーネルによる環境変数の扱い

  • 新プロセス起動時、 カーネルは環境変数をスタック上に連続したヌル終端文字列配列 として配置
  • このレイアウトは静的で、プログラムは自身でコピーして管理する必要
  • 例: HOME=/, PATH=/usr/bin などが16進表示で並ぶ

各言語による環境変数の内部管理

  • Bash
    • ハッシュマップのスタック構造 で管理
    • localでスコープ付き変数、exportで子プロセスに伝播
    • ローカル変数もexport可能
  • glibc(C言語)
    • 動的な配列environ で管理
    • putenvgetenvで操作、線形探索なので高速ではない
  • Python
    • os.environ はCのenviron配列から構築
    • os.environの変更はos.putenvを呼ぶが、逆方向は同期されない
    • os.environとCライブラリの間に一貫性のズレ が生じる場合あり

環境変数のフォーマットと制限

  • Linuxカーネルやglibcは フォーマットに寛容
    • 同名変数の重複や、=なしのエントリも許容
    • 例: NONSENSE_WITH_EMOJI 😀 のような値も受け入れる
  • 制限事項
    • 1変数あたり128KiB (x64 Intel CPUの場合)
    • 全体で2MiB (コマンドライン引数と共有)
    • 制限はスタックサイズやページサイズによる

環境変数の挙動の違いと注意点

  • Bashは 重複名や不正フォーマットを自動で整理
  • 変数名に空白を含む場合、NushellやPythonは許容、Bashは参照不可だが子プロセスには渡る
  • 不正な変数はBashの invalid_envハッシュマップ に格納

POSIX標準と推奨される命名規則

  • POSIX では変数名に=が含まれていなければ許容
  • POSIX準拠アプリは 大文字・数字・アンダースコア のみ使用
  • 小文字やその他の文字も許容されるが、標準ツールとの衝突回避のため 小文字はアプリ独自用途に予約
  • 実際は ALL_UPPERCASE が実務的に推奨される
  • 安全な命名: ^[A-Z_][A-Z0-9_]*$、値はUTF-8またはPOSIX Portable Character Set

まとめと実践的アドバイス

  • 環境変数は 便利だが古い設計 であり、 名前空間や型安全性はない
  • 大文字+アンダースコア+数字 の形式で命名し、値はUTF-8推奨
  • パフォーマンスや安全性 を求める用途では過度な利用を避ける
  • 言語やシェルごとの違い に注意し、意図しない伝播や競合を防ぐ設計が重要

Hackerたちの意見

環境変数はよく秘密情報を渡すために使われるけど、普及している割には良くないプラクティスだと思う。 - Linuxシステムでは、どのユーザープロセスでも同じユーザーの他のプロセスの環境変数を確認できる。脅威モデルについて議論はできるけど、特に開発者のシステムでは、同じユーザーとして動いているプロセスがめっちゃ多い。 - 個人的には、非コンテナ化されたLLMエージェントが開発者のメインOSユーザーと同じユーザースペースで動くようになったことで、これがさらに顕著な問題になってると思う。これは秘密情報を抜き取る悪用者にとって夢のような状況だ。 - 環境変数は通常、他の生成されたプロセスに渡されるけど、実際に必要なのは主プロセスだけのことが多い。 - systemdはユニットの環境変数を全てのシステムクライアントにDBUSを通じて公開していて、秘密に環境変数を使うことを警告している[1]。これって、非ルートユーザーがルート専用のユニットやサービスに設定された環境変数にアクセスできるってことだと思う。間違ってるかもしれないけど、まだ試してないからね。でも、これが本当なら、多くのシステム管理者には大きな驚きだろうな。秘密をファイルや環境変数に出さずに管理するためには、秘密管理プロセス(例えば1Passwordのop CLIツール)と、その秘密が必要なプロセス(flaskやterraformなど)との間で一時的なファイル共有をするのが唯一の解決策だと思う。これがsystemdの資格情報システムの仕組みなんだけど、広くサポートされてるわけじゃない。環境変数や通常のプレーンテキストファイルを使わずに秘密を渡す良い方法はないかな? 編集: 1Passwordのopクライアントは、各新しい「セッション」が自分の承認を必要とするので、いいスタートだと思う。だから、秘密が必要なCLIセッションでそのツールを有効にできるけど、opバイナリを使おうとする悪意のあるプロセスはその承認を利用できない。新しいポップアップが出るからね。でも、これはまだ第一歩。第二歩は…その秘密を必要なプロセスとどう共有するかで、また元の議論に戻る。

Linuxシステムでは、どのユーザープロセスでも同じユーザーの他のプロセスの環境変数を確認できる。脅威モデルについて議論はできるけど、特に開発者のシステムでは、同じユーザーとして動いているプロセスがめっちゃ多い。 これはすごく良いポイントだね!でも、どうやって回避するのかは分からないな。そのプログラムが認証情報を見つけてファイルを復号化できるなら、ユーザーとして動いてる限り、他のプロセスもその認証情報を見つけられるってことだし。

環境変数を使わずに秘密を共有する良いクロスプラットフォームで簡単な方法はないかな?

Linuxのセキュリティモデルは、ネームスペースなしではかなり壊れてる。systemdには役立つ機能がいくつかあるけど、環境変数よりも良いものを求めるなら、自然とcgroupsに手が伸びるよね。

環境変数や通常のプレーンテキストファイルを使わずに秘密を渡す良い方法はないかな? memfd_secretが思い浮かぶね https://man7.org/linux/man-pages/man2/memfd_secret.2.html でも、あまり言語サポートは見たことがないな。おそらくLinux専用だからかも。Rustで書く人たち(あとはGoも、FFIがどれだけ簡単かによるけど)は試してみるべきだと思う。Cの関数をラップするのは簡単だろうから、PHPでもサポートを得たいと思ってたけど、php-fpmを変更しなきゃいけないって考えるとちょっと躊躇しちゃった。Cコードをハックしたくないし、できないからね。実際には、プロセスマネージャーが秘密ファイルディスクリプタを開いた後に子プロセスを生成して、それを渡すのが理想だね。可視メモリにも/proc/*/environにも出さない形で。

少なくとも2012年から、環境変数は普通のメモリと同じくらい安全になってるよ:コミットb409e578d9a4ec95913e06d8fea2a33f1754ea69 著者:Cong Wang 日付:2012年5月31日木曜日 16:26:17 -0700 proc: /proc//environの処理を整理する 他のプロセスの環境を読むことはできないけど、そのプロセスをptrace-readできれば、そのプロセスの秘密は全部わかるからね。cmdlineはまた別の話だよ。

答えではないけど、秘密を渡すための低レベルのプリミティブとそれに対応する高レベルの言語構造があればいいなと思う。例えば、my_secret = create_secret(value) みたいな感じで。そうすれば、その時点からは不透明な値になるのが理想だね。

環境変数は秘密を渡すためによく使われる。でも、その普及にもかかわらず、それは悪い習慣だと思う。環境変数は、コンテナオーケストレーションシステムで管理されるコンテナ化されたアプリケーションの設定パラメータや秘密を渡すために推奨されているんだ。設計上、他のプロセスはコンテナ内で実行されている環境変数を検査できないし、環境変数は子プロセスに渡されるのは、設計上、親プロセスと同じ環境(つまり、同じ値)で子プロセスを実行することが目的だからだよ。さらに、子プロセスを生成するプロセスが環境変数を設定する責任があるから、すでにその秘密に対して少なくとも読み取りアクセスを持っていることになる。全体的に見て、君の懸念は根拠のない理由に基づいていると思うけど、詳しく話し合うのは大歓迎だよ。

  • Linuxシステムでは、どのユーザープロセスでも同じユーザーの他のプロセスの環境変数を調べることができる。脅威モデルについて議論できるけど、特に開発者のシステムでは、同じユーザーとして実行されているプロセスがたくさんあるんだ。ただ、ほとんどのオペレーティングシステムのセキュリティモデルは、ユーザーとしてプロセスを実行することはそのユーザーとして行動することを意味する。いくつかの注意点はあるけど(FreeBSDにはcapsicum、Linuxにはlandlock、SELinux、AppArmor、Windowsには整合性ラベルがある)、一般的には、誰かに何かを実行させることができれば、そのプログラムはそのユーザーの代理として行動する権限を委譲される。 (多くのシステムのユーザーアカウントには他のユーザーを偽装する権限がある場合もある。)これは唯一のセキュリティモデルではないけど(完全に能力ベースのオペレーティングシステムも存在する)、ほとんどのコンピューティング形式で使われているセキュリティモデルなんだ。これの一つの結果として、自分のドメイン内の何でも制御できる。自分のプロセスを終了させたり、スリープさせたり、そして何よりもデバッグできる。秘密を持っているものは、ptrace/process_vm_readv/ReadProcessMemory/etcでそれを取得できる。

あなたが説明したのは、古典的なUnixのセキュリティモデルで、少しの改善があるね。時代に合わせてはいるけど、古さが目立ってきた。特に、安価で普及したコンピューティングに適応するのが難しいっていうのが、元々設計されていなかったから。もし他のプロセスから秘密を守りたいなら、同じユーザーアカウントで実行しない方がいいよ。リモートでアクセスするのも一つの手だけど、他のトレードオフや難しさが伴うからね。

環境変数や普通のプレーンテキストファイルを使わずに秘密情報を渡す良い方法はない? プレーンテキストファイルは問題ないけど、そのファイルの権限が問題なんだよね。プログラムのソースをコントロールできるのが一番良い方法で、そうすれば秘密情報が漏れないように、秘密情報を読めるユーザーとしてプログラムを起動するように変更できる。起動後はプログラムがファイル全体を読み込んで、すぐに権限を落として秘密情報を読めないユーザーに切り替えるんだ。秘密情報だけじゃなくて、他のことにも使えるよ。

面白い読み物だね。もう一つ興味深い事実は、setenv()がPOSIXで根本的に壊れていて、ライブラリコードでは基本的に呼び出すべきじゃないってこと。アプリケーションコードでは、代替手段がない場合にのみ呼び出すべきで、スレッドが始まる前に呼び出すべきだ。理由は、getenv()が変数への生ポインタを渡すから、setenv()で変数を上書きすることは防げないから。極めて注意して扱うべきだね。

Hacker Newsで議論の続きを見る