パス走査・プレビュー脆弱性の修正と render_in オプション化を含む 3 つのセキュリティバックポート

viewcomponent/view_component

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_fromrender_not_found メソッドを追加し、スタックトレースが外部に漏れないようにしました。

設計判断: 既存の raise だけでなく HTTP ステータスコードで安全に応答する方針を取ったことで、API 互換性は保ちつつもクライアント側でエラーハンドリングが容易になりました。パス比較ロジックをシンプルに保ちつつ、File::SEPARATOR を明示的に使用した点が後方互換性の鍵です。

プレビュールート脆弱性の修正

背景: ViewComponent::Previewrender_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.rbrender_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_inInstrumentation#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 の安定性を高める実務的な選択であり、コードロジックに影響を与えない安全な変更です。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
52fa4fc5

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ⚠ WARNING

Title, Context, Technical Detailの存在と明確さ

リード文はあるが、記事全体のまとめ(結論)が欠如している。各セクションは背景・技術変更・設計判断で構成されているが、最後に全体を総括する段落がない。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのファイル名付きシンタックスハイライトは正しいが、PRへのリンク記法が `[PR #2650](URL)` となっており、要求された `[#2650](URL)` 形式になっていない。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門的なエンジニア向けの記述で、余計な初心者向け解説はない。適切な読者層に合わせた技術レベルである。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の構成が保たれ、段落はトピックセンテンスで始まり、1段落1トピックが守られている。段落間の空行も適切。

Diff内容との照合 ⚠ WARNING

コードブロックとDiff内容の一致

ほとんどのコードブロックは Diff 内容と一致しているが、`app/controllers/view_components_system_test_controller.rb` のブロックで `raise` 行が追加行として表示されており、実際の Diff では変更されていない点が誤っている。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている技術用語は正確で、PR で触れられている概念と合致している。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

説明は Diff と PR の変更点を正しく反映しており、根拠のある技術的主張が行われている。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事のすべての主張は PR のタイトル・説明・Diff に裏付けられており、外部情報や推測は含まれていない。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #2650 などの数値は正確。その他数値的な記述はなし。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の「3 x security backports」の内容を日本語で適切に再表現している。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

LTS やリリース日程等、PR に記載されていない外部知識は記事に含まれていない。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と合致している。