Spring server起動時の環境変数がクライアントアプリに漏洩する問題を修正
Springサーバーがspawn_on_envで指定された環境変数を持った状態で起動された場合、その環境変数が後続のクライアントアプリに意図せず引き継がれる問題が修正されました。この変更により、クライアント側で環境変数を設定しなくても、サーバー起動時の環境変数がアプリに漏洩することがなくなります。
背景
spawn_on_env は、環境変数の値が変わったときにSpringアプリを再起動させるための機能です。しかし、サーバー起動時に設定された環境変数が、その後のクライアント実行時に意図せず引き継がれる問題が存在していました。
具体的には、以下のような状況で問題が発生します。最初に環境変数を設定してコマンドを実行すると:
VAR_FROM_BOOT=before bin/rails ...
その後、環境変数なしでコマンドを実行した場合でも、アプリは VAR_FROM_BOOT=before が設定された状態で起動していました:
bin/rails ...
さらに、Springはクライアント接続時にこの変数を削除することで、正しい環境変数が設定されているように見せかけていました。これはspawn_on_envの目的である「異なる環境でアプリを起動する」という機能と矛盾する動作です。
技術的な変更
問題の原因は、spawn_on_envの値がクライアントからサーバー、サーバーからアプリへと伝達される過程にありました。
変更前の処理フロー:
- クライアントは
ENVからspawn_on_envのキーをスライスして取得(環境変数が未設定の場合は空のHash) - サーバーは受け取った空のHashを使ってアプリをfork(環境変数の削除は行われない)
- forkされたアプリはサーバーの
ENVを継承するため、VAR_FROM_BOOTが設定された状態で起動 - クライアントがアプリプロセスに接続後、サーバー由来の環境変数をクリーンアップ
lib/spring/client/run.rbでの修正:
def spawn_env
Spring.spawn_on_env.to_h do |key|
[key, ENV[key]]
end
end
変更前はENV.slice(*Spring.spawn_on_env)を使用していたため、未設定のキーは結果のHashに含まれませんでした。変更後はto_hを使い、spawn_on_envで指定されたすべてのキーについて、その値(nilを含む)を明示的にHashに含めるようになりました。
lib/spring/application_manager.rbでの修正:
"SPRING_SPAWN_ENV" => JSON.dump(spawn_env.compact),
**spawn_env,
SPRING_SPAWN_ENV環境変数(spring statusコマンドで表示される情報)にはcompactを適用してnil値を除外します。これにより、表示が冗長になることを防ぎます。一方、**spawn_envでスプレッドされる実際の環境変数にはnil値も含まれるため、forkされたアプリで適切に環境変数がクリアされます。
テストケースの追加:
test "spawn_on_env variables are cleared when unset" do
File.write(app.spring_client_config, "Spring.spawn_on_env << 'VAR_FROM_BOOT'")
File.write(app.application_config, "#{app.application_config.read}\nRails.configuration.x.var_from_boot = ENV['VAR_FROM_BOOT']")
app.env["VAR_FROM_BOOT"] = "before"
assert_success %(bin/rails runner 'p Rails.configuration.x.var_from_boot.inspect'), stdout: "before"
app.env.delete "VAR_FROM_BOOT"
assert_success %(bin/rails runner 'p Rails.configuration.x.var_from_boot.inspect'), stdout: "nil"
end
このテストは、環境変数が設定された状態でサーバーを起動した後、その変数を削除してもアプリに漏洩しないことを検証します。
設計判断
nil値を明示的に含める方式が採用されました。ENV[key]は存在しないキーに対してnilを返すため、to_hでマッピングすることで、設定されていない環境変数もspawn_env Hashにnilとして含まれます。
このアプローチにより、サーバーからアプリへforkする際に、Rubyの環境変数設定メカニズム(nil値は環境変数を削除する)を活用できます。**spawn_envでスプレッドされたnil値は、forkされたプロセスで対応する環境変数を確実にクリアします。
一方、SPRING_SPAWN_ENVにはcompactを適用することで、ステータス表示用の情報にはnil値を含めない判断がなされています。これは情報表示の目的と、実際の環境変数制御の目的を分離した設計といえます。
まとめ
本PRは、spawn_on_envのHash構築方法を変更することで、Springサーバーの環境変数がクライアントアプリに漏洩する問題を解決しました。未設定の環境変数をnilとして明示的に含めることで、fork時の環境変数削除を確実にし、各クライアントが独立した環境で実行されることを保証しています。