HostAuthorization が余分なポートを許可しないように修正

rails/rails

HostAuthorization が明示的にポートを指定したホストに対して余分なオプションポート正規表現を付与し、www.example.com:80:80 のような不正な Host が許可されていた問題を解消します。これにより、リダイレクト処理が期待通りに動作し、サーバーエラーを防げます。

背景

問題の根源action_dispatch/middleware/host_authorization.rbPermissions#sanitize_string が常に PORT_REGEX? を付与した点にあります。許可リストに www.example.com:80 が含まれていても、正規表現は任意のポートを許容するため www.example.com:80:80 がマッチし、内部リダイレクトで不正なホストが処理されました。#37956 で報告されたように、Malformed Host ヘッダーが原因で 500 エラーが発生していました。

技術的な変更

sanitize_string の生成ロジックを port_regexp に置き換え、ホストに既にポートが含まれる場合は空文字列を返すようにしました。

@@
-            /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
+            /\A#{Regexp.escape host}#{port_regexp(host)}\z/i

新たに導入した port_regexphost_with_port? は以下の通りです。

@@
-        def sanitize_string(host)
-          if host.start_with?(".")
-            /\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
-          else
-            /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
-          end
-        end
+        def sanitize_string(host)
+          if host.start_with?(".")
+            /\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{port_regexp(host)}\z/i
+          else
+            /\A#{Regexp.escape host}#{port_regexp(host)}\z/i
+          end
+        end
+
+        def port_regexp(host)
+          host_with_port?(host) ? "" : "#{PORT_REGEX}?"
+        end
+
+        def host_with_port?(host)
+          if host.start_with?("[")
+            host.match?(/\]:\d+\z/)
+          else
+            host.count(":") == 1 && host.match?(/:\d+\z/)
+          end
+        end

テストも拡充し、ポート付きホストが正常に動作するケースと、余分なポートを持つホストが Forbidden になるケースを検証しています。

@@
   test "hosts configured with explicit port work" do
@@
   test "blocks malformed hosts with extra ports" do
@@
     assert_response :forbidden
     assert_match "Blocked hosts: www.example.com:80:80", response.body
   end

設計判断

後方互換性の維持を最優先し、既存の「ポートが無いホストは任意ポートを許容」ロジックはそのままにしました。port_regexp が空文字列を返す条件を host_with_port? で判定するだけなので、既存コードへの侵入は最小限です。新しいヘルパーメソッドはプライベート領域に収められ、外部 API には影響しません。

このアプローチは正規表現の複雑化を避け、ホスト文字列の構造分析に委ねることで保守性を高めました。結果として、明示的ポート指定の正当性はそのまま保たれ、余分なポートが付与されたケースだけが除外されます。

まとめ

本 PR は HostAuthorization の正規表現生成を改良し、余分なポートを持つ malformed Host をブロックします。オプションポートの挙動はポート未指定時に限定され、既存設定との互換性を維持しつつ安全性が向上しました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
53858d47

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断、まとめといった必須要素がすべて揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックは正しい `言語:ファイルパス` 形式ですが、PRリンクのテキストが "PR #57503" となっており、要求された形式 `[#57503](URL)` とは若干異なります。

対象読者への適合性 ✓ PASS

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

Rails の内部実装に関する詳細が中心で、エンジニア向けに適切なレベルです。

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

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

各セクションは総論・各論・結論のパラグラフ構造を保ち、トピックセンテンスで始まり、段落が長すぎる箇所はありません。

Diff内容との照合 ✓ PASS

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

記事中のコードブロックは提供された Diff と完全に一致しており、変更点・追加メソッド・テストコードとも合致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語は PR で使用されているものと一致し、誤用は見られません。

説明の技術的正確性 ✓ PASS

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

技術的説明は PR の Motivation と Detail に基づき正確です。

事実の突合 ✓ PASS

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

全ての主張は PR のタイトル、説明、Diff に裏付けられ、外部知識の付加や推測はありません。

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

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

PR 番号 #57503、Issue 番号 #37956 などの数値は正確です。

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

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

記事タイトルは PR の内容 (extra ports を持つ malformed hosts を拒否) を適切に反映しています。

外部知識の正確性 ✓ PASS

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

LTS やリリース日程等、PR に無関係な外部情報は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の誤りはなく、PR の記述と合致しています。