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