パス走査・プレビュー脆弱性の修正と render_in オプション化を含む 3 つのセキュリティバックポート
ViewComponent 3.25.0 では、テストコントローラのパス走査脆弱性、プレビュールートの任意テンプレート実行脆弱性、そしてコンポーネントインスタンスのステイルコンテキスト問題に対して、3 つのセキュリティバックポートが実装されました。これにより、攻撃者が不正なファイルにアクセスしたり、意図しないテンプレートをレンダリングしたりするリスクが除去され、render_in のシグネチャが Rails 本体と整合性を持つようになりました。
パス走査脆弱性の修正
背景: ViewComponentsSystemTestController は一時ディレクトリ配下のファイルを直接読み込むため、start_with? による単純な文字列比較だけでは、同名プレフィックスを持つ別ディレクトリへの遡りが可能でした。これがパス走査 (path traversal) 攻撃に繋がる可能性がありました。
技術的な変更: app/controllers/view_components_system_test_controller.rb において、許可ディレクトリのプレフィックス判定を セパレータ意識型 に変更しました。
- unless @path.start_with?(base_path)
- raise ViewComponent::SystemTestControllerNefariousPathError
- end
+ allowed_prefix = "#{base_path}#{::File::SEPARATOR}"
+ unless @path == base_path || @path.start_with?(allowed_prefix)
+ raise ViewComponent::SystemTestControllerNefariousPathError
+ end
さらに、例外ハンドリングで 404 を返すよう rescue_from と render_not_found メソッドを追加し、スタックトレースが外部に漏れないようにしました。
設計判断: 既存の raise だけでなく HTTP ステータスコードで安全に応答する方針を取ったことで、API 互換性は保ちつつもクライアント側でエラーハンドリングが容易になりました。パス比較ロジックをシンプルに保ちつつ、File::SEPARATOR を明示的に使用した点が後方互換性の鍵です。
プレビュールート脆弱性の修正
背景: ViewComponent::Preview の render_args は、プレビューサブクラスに定義されていないメソッドでも URL パラメータで呼び出せてしまい、任意の内部テンプレートがレンダリングされる危険がありました。これはプレビュー URL が攻撃者に利用され得る典型的なリモートコード実行パターンです。
技術的な変更: lib/view_component/preview.rb に検証ロジックを組み込み、許可された例示メソッド以外が要求された場合は AbstractController::ActionNotFound を即座に発生させます。
- result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)
+ raise AbstractController::ActionNotFound, "#{example} is not a valid preview example" unless examples.include?(example.to_s)
+ result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)
この例外は Rails の標準ハンドラで 404 に変換され、意図しないテンプレートが外部へ漏れることはなくなります。
設計判断: 明示的な許可リスト (examples) に基づく検証を追加したことで、ホワイトリスト方式 を採用し、将来のプレビューメソッド追加時も安全に拡張できる設計となっています。例外を ActionNotFound に統一したことで、他のコントローラと同様のエラーハンドリングが流用可能です。
コンポーネントステイルコンテキストと render_in シグネチャの統一
背景: コンポーネントインスタンスは一度レンダリングされた後、@view_context など多数のインスタンス変数を保持したまま再利用されることがあり、別リクエスト間でデータが漏洩するリスクがありました。また、Rails 本体が render_in(view_context, **options, &block) へシグネチャを変更したことに対し、ViewComponent が旧シグネチャを保持していました。
技術的な変更: lib/view_component/base.rb の render_in を新しいシグネチャに合わせ、毎回コンテキストをリセット するロジックを導入しました。
- def render_in(view_context, &block)
+ def render_in(view_context, **options, &block)
self.class.compile(raise_errors: true)
+ __vc_reset_render_state!
@view_context = view_context
- self.__vc_original_view_context ||= view_context
+ self.__vc_original_view_context =
+ if instance_variable_defined?(:@__vc_pending_original_view_context)
+ remove_instance_variable(:@__vc_pending_original_view_context)
+ else
+ view_context
+ end
@@
- @lookup_context ||= view_context.lookup_context
+ @lookup_context = view_context.lookup_context
@@
- @view_renderer ||= view_context.view_renderer
+ @view_renderer = view_context.view_renderer
@@
- @view_flow ||= view_context.view_flow
+ @view_flow = view_context.view_flow
同様に、Collection#render_in と Instrumentation#render_in も新シグネチャへ合わせ、子コンポーネントのインスタンスは毎回再生成するように変更しました。
設計判断: 既存インスタンス変数の残存を防ぐために __vc_reset_render_state! を呼び出す点と、@__vc_pending_original_view_context を利用して元コンテキストの上書きを防ぎつつ、後方互換性を保つ点が重要です。また、シグネチャ統一により Rails 本体との API 整合性 が確保され、将来の Rails アップグレード時の破壊的変更リスクが低減されました。
補足的な CI とテスト環境の調整
背景: Ruby 3.5 で Nokogiri のバイナリが未提供になるケースに備えて、CI で強制的にソースビルドを走らせる必要がありました。また、bundle install へ統一し、bundle update の実行を排除しました。
技術的な変更: .github/workflows/ci.yml に環境変数 BUNDLE_FORCE_RUBY_PLATFORM を条件付きで設定し、.github/workflows/lint.yml では bundle install に差し替えました。
+ BUNDLE_FORCE_RUBY_PLATFORM: ${{ matrix.ruby_version == '3.5' && 'true' || 'false' }}
- bundle update
+ bundle install
設計判断: ビルド失敗を防ぐための環境フラグ追加は、CI の安定性を高める実務的な選択であり、コードロジックに影響を与えない安全な変更です。