[Rails] HTTPローカル環境でCSRFヘッダーオンリー保護を利用可能に

rails/rails

背景: セキュアコンテキストの制約とローカル環境の問題

RailsのCSRF保護では、Sec-Fetch-Siteヘッダーを検証するヘッダーオンリーアプローチが提供されています。しかし、HTTPSを使用しないローカルネットワーク環境では、ブラウザがセキュアコンテキストとして認識しないため、Sec-Fetch-Siteヘッダーが送信されません。この結果、非GETリクエストがCSRF保護により拒否され、ローカル環境でのアプリケーション利用に支障をきたしていました。

この問題はFizzyプロジェクトのディスカッションで報告され、PR #2291で解決されました。本PRはその変更をRails本体にアップストリームしたものです。

技術的変更内容

CSRF検証ロジックの拡張

verified_via_header_only?メソッドに新たな条件分岐が追加されました。

変更前:

def verified_via_header_only?
  SAFE_FETCH_SITES.include?(sec_fetch_site_value) ||
    (sec_fetch_site_value == "cross-site" && origin_trusted?)
end

変更後:

def verified_via_header_only?
  SAFE_FETCH_SITES.include?(sec_fetch_site_value) ||
    (sec_fetch_site_value == "cross-site" && origin_trusted?) ||
    (sec_fetch_site_value.nil? && !request.ssl? && !ActionDispatch::Http::URL.secure_protocol)
end

新たに追加された条件は以下の3つを同時に満たす場合にリクエストを許可します:

  1. sec_fetch_site_value.nil? - Sec-Fetch-Siteヘッダーが存在しない
  2. !request.ssl? - HTTPリクエストである
  3. !ActionDispatch::Http::URL.secure_protocol - アプリケーションでSSL強制が無効

重要な点として、Originヘッダーの検証は既存のorigin_trusted?チェックとは独立して常に実行されます。

テストケースの追加

ローカルHTTP環境での動作を保証するため、複数のテストケースが追加されました。

test "allows POST with missing Sec-Fetch-Site header on HTTP when force_ssl is disabled" do
  with_secure_protocol(false) do
    post :index
    assert_response :success
  end
end

test "blocks POST without Sec-Fetch-Site header when request is HTTPS" do
  @request.set_header "HTTPS", "on"
  assert_raises(ActionController::InvalidCrossOriginRequest) do
    post :index
  end
end

test "blocks POST without Sec-Fetch-Site header when request is HTTP but force_ssl is enabled" do
  with_secure_protocol(true) do
    assert_raises(ActionController::InvalidCrossOriginRequest) do
      post :index
    end
  end
end

これらのテストにより、以下のシナリオが検証されます:

  • HTTP + force_ssl無効: リクエスト許可
  • HTTPS環境: リクエスト拒否(既存の安全な動作を維持)
  • HTTP + force_ssl有効: リクエスト拒否(本番環境相当の設定では厳格に検証)

既存テストの調整

セッション・Cookie関連のミドルウェアテストでは、CSRF検証を明示的に失敗させるためSec-Fetch-Site: cross-siteヘッダーが追加されました。

header "Sec-Fetch-Site", "cross-site"

get "/foo/write_session"
get "/foo/read_session"
assert_equal "1", last_response.body

post "/foo/read_session"  # Read session using POST request failing CSRF check
assert_equal "nil", last_response.body

この変更により、テストの意図がより明確になり、ヘッダー不在時の新しい許可ロジックの影響を受けなくなりました。

セキュリティ上の考慮事項

この変更は、以下の理由により既存のセキュリティレベルを維持しています:

  1. 限定的な適用範囲: HTTPかつforce_ssl無効という、明確にローカル環境を想定した条件下でのみ動作
  2. Origin検証の継続: Sec-Fetch-Siteヘッダーの有無に関わらず、Originヘッダーの検証は常に実行される
  3. 本番環境への影響なし: HTTPS環境やforce_sslが有効な本番環境では従来通りの厳格な検証が継続

影響を受けるユースケース

以下のような環境でヘッダーオンリーCSRF保護が利用可能になります:

  • 社内ネットワークでHTTPを使用するアプリケーション
  • 開発環境でHTTPSを使用しない場合(config.force_ssl = false
  • HTTPでアクセスされるイントラネットアプリケーション

これらの環境でも、クロスサイトリクエストからの保護はOriginヘッダー検証により維持されます。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成の必須3要素(Title, Context, Technical Detail)がすべて明確に記載されています。コードブロック前後の空行やファイル名付きシンタックスハイライトなど、カスタムMarkdown構文も正しく使用されており、可読性が高いです。対象読者であるエンジニアに適した技術レベルで書かれています。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

記事で引用されているコードはすべてDiff内に存在し、改変なく正確に反映されています。`verified_via_header_only?`メソッドの変更ロジックや追加されたテストケースの説明は、技術的に正確かつ論理的です。`Sec-Fetch-Site`や`セキュアコンテキスト`などの技術用語も正しく使用されています。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

PRの背景(Fizzyプロジェクトからのアップストリーム)、変更の目的、セキュリティ上の考慮事項など、PRのDescriptionで説明されている内容が忠実に記事に反映されています。PR番号や関連するディスカッションへのリンクも正確で、ハルシネーション(根拠のない情報)は見られません。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除