ActiveModelの整数キャストに文字列長の上限を設定してDoS対策を強化

rails/rails

ActiveModel::Type::Integerto_i を呼び出す前に文字列を切り詰めるようになり、非常に長い文字列を整数にキャストする処理がDoS攻撃のベクタになり得る問題を緩和します。

背景

Rubyの to_i は非常に長い文字列に対しても律儀に全体を走査するため、数MBの文字列を渡すと処理に時間がかかります。ActiveRecordはHTTPパラメータを整数カラムにキャストする際にこのパスを通るため、ユーザーが意図的に巨大な文字列を送信することでサーバーリソースを枯渇させられる可能性がありました。

PR本文に示されたように、以下のようなコードが脆弱なパターンです。

Post.where(id: params[:id]).first

params[:id] に5MBの数字文字列が渡されると、従来の実装ではキャスト時に文字列全体を走査していました。ユーザーがモデル側で入力長を検証することが本来の対策ですが、ActiveModel層でも自動的に上限を設けることで多層防御を実現します。

技術的な変更

activemodel/lib/active_model/type/integer.rbcast_value メソッドが拡張され、文字列に対して to_i を呼ぶ前に _limit * 4 バイトへの切り詰めを行うようになりました。

変更前:

def cast_value(value)
  value.to_i rescue nil
end

変更後:

def cast_value(value)
  case value
  when ::Integer
    value
  when ::String
    str = value.bytesize > _limit * 4 ? value.byteslice(0, _limit * 4) : value
    str.to_i rescue nil
  else
    value.to_i rescue nil
  end
end

_limit はカラムのストレージサイズ(バイト数)です。デフォルトの4バイト整数では上限は16バイト、8バイトの bigint では32バイトになります。この値は、各型が表現できる最大値の桁数(4バイト整数で10桁、8バイト整数で19桁)を十分カバーしつつ、符号文字や短いスラグサフィックスのぶんの余裕も持たせた設計です。

また、値がすでに ::Integer である場合は to_i を呼ばずに直接返す最適化も合わせて導入されています。これにより、整数値のキャストではメソッド呼び出しのオーバーヘッドが削減されます。

追加されたテストは以下の2ケースをカバーしています。

  • 上限を超える17バイトの文字列は先頭16バイトのみが to_i の対象になること
  • 16バイト以下の文字列および "42-some-slug" のようなスラグ形式は従来どおりキャストされること

設計判断

上限の係数として 4 が選ばれた理由は、ActiveRecordの「auto integerization(スラグの自動整数化)」との互換性維持にあります。

当初の検討では、データベースのカラム幅をそのまま上限にする案もありましたが、"123-hello-world-etc" のようなスラグ形式をパラメータとして受け取ってそのまま整数カラムの検索に使うパターンが広く使われているため、この案は採用されませんでした。_limit * 4 はスラグサフィックスを許容しつつも、数MBレベルの文字列を切り詰めるのに十分な上限です。

また、切り詰めが発生しても to_i は先頭の数値部分のみを解釈するRubyの標準的な挙動に依存しており、"1111111111111111" * N のような文字列が意図どおり先頭16バイトの数値として解釈されます。例外を発生させるのではなく、黙って切り詰めて継続する方針は、既存のエラーハンドリング(rescue nil)と一貫しています。

まとめ

本PRは、ActiveModelの整数キャストに軽量なバイト数チェックを加えることで、コードの変更を最小限に抑えながらDoSリスクを大幅に低減しています。スラグ互換性を維持するために _limit * 4 という係数を選んだ設計判断は、既存のユースケースを壊さずにセキュリティ境界を設けるバランスの取れたアプローチです。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
eb425133

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という理想的な3部構成が明確に守られています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト、PR番号のリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

内容は専門知識を持つエンジニア向けに書かれており、冗長な説明がなく適切です。

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

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

各セクションが総論→各論で構成され、段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

変更後のコードはDiffと完全に一致しています。変更前のコードも文脈を補うために適切に表現されており、Diffの内容を正確に反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`DoS`, `auto integerization`, `slug` などの技術用語がPRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

`_limit * 4` の意味や、`::Integer` の場合の最適化など、コード変更に関する説明が技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張(DoSの危険性、`* 4`という係数の採用経緯など)は、提供されたPR Descriptionで裏付けられており、ハルシネーションは見られません。

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

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

PR番号(#57368)、バイト数(16, 32)、係数(4)などの数値や固有名詞はすべて正確です。

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

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

記事タイトルはPRの主題「文字列長の上限設定」と、その目的「DoS対策」を的確に要約しており、PR内容と完全に一致しています。

外部知識の正確性 ✓ PASS

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

PR情報に記載のないバージョンサポート情報やリリース日程などの外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

完了した変更を説明する「〜するようになり」「導入されています」といった時間表現は、PRの文脈と一致しており正確です。