Mailgunインバウンドメール署名の不正パラメータを適切に拒否する修正

rails/rails

Mailgunイングレスにおいて、signatureパラメータが文字列以外の型(配列など)で送信された場合、401 Unauthorizedを返す代わりにNoMethodErrorが発生していたバグを修正しました。1行の型チェック追加により、不正なパラメータ形状を認証失敗として正しく扱えるようになります。

背景

Mailgunイングレスのsigned?メソッドは、リクエストパラメータから取得したsignature値をActiveSupport::SecurityUtils.secure_compareに直接渡していました。この実装はsignatureが常に文字列であることを前提としており、配列などの非文字列型が渡された場合の考慮がありませんでした。

HTTPリクエストのパラメータ解析では、同名パラメータが複数送信された場合や特定のエンコード形式によって、値が文字列ではなく配列として解釈されることがあります。Issue #57484で報告されたように、signatureが配列として渡されるとsecure_compare内部で.bytesizeを呼び出す際に以下のエラーが発生していました:

NoMethodError: undefined method `bytesize' for an instance of Array
    activesupport/lib/active_support/security_utils.rb:34:in `secure_compare'
    app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb:98:in `signed?'

このエラーはコントローラーが401 Unauthorizedを返す前に発生するため、不正なリクエストが認証失敗として処理されるべきところを、アプリケーションエラーとして露出させてしまっていました。

技術的な変更

修正はactionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rbsigned?メソッドに対して、1行の変更のみで行われています。

変更前:

def signed?
  ActiveSupport::SecurityUtils.secure_compare signature, expected_signature
end

変更後:

def signed?
  signature.is_a?(String) && ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
end

signature.is_a?(String)によるガード節を&&の短絡評価で先行させることで、文字列以外の型が渡された場合はsecure_compareの呼び出しをスキップし、falseを返します。signed?falseを返すとauthenticated?falseとなり、コントローラーは401 Unauthorizedを返します。

リグレッションテストとして、配列型のsignatureパラメータを含むリクエストが401 Unauthorizedを返し、ActionMailbox::InboundEmailが作成されないことを検証するテストケースも追加されています:

test "rejecting an inbound email from Mailgun with a malformed signature" do
  assert_no_difference -> { ActionMailbox::InboundEmail.count } do
    travel_to "2018-10-09 15:15:00 EDT"
    post rails_mailgun_inbound_emails_url, params: {
      timestamp: 1539112500,
      token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
      signature: [ "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc" ],
      "body-mime" => file_fixture("../files/welcome.eml").read
    }
  end

  assert_response :unauthorized
end

設計判断

secure_compareの呼び出し前に型チェックを置く設計が採用されました。

secure_compareは定数時間比較(タイミング攻撃対策)を目的とした関数であり、その内部で型チェックを行うのは関数の責務を超えます。呼び出し側で事前に型を保証することで、セキュリティユーティリティの責務境界を明確に保っています。また、is_a?(String)Stringのサブクラスも許容するため、必要以上に制限的ではありません。

ショートサーキット評価(&&)を使うことで、型チェックが失敗した場合はsecure_compareの実行コストも発生しない点も合理的です。セキュリティの文脈では「不正な入力は早期に拒否する」原則に沿った実装といえます。

まとめ

本PRは、外部からの不正なパラメータ形状がアプリケーション内部のエラーとして漏出するという防御的設計の欠陥を、最小限の変更で修正しています。型チェックによるガード節の追加という単純な手法で、セキュリティ的にクリーンな失敗(401 Unauthorized)を保証しています。

記事メタデータ

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

この記事は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番号・Issue番号のリンク記法がガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

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

「イングレス」「secure_compare」「タイミング攻撃」などの専門用語を前提としており、対象読者であるエンジニアに適切なレベルの内容です。

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

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

各セクション、各パラグラフが「総論→各論」の構成で書かれており、トピックセンテンスも明確です。段落の長さも適切で、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコード(コントローラとテスト)は、提供されたDiff情報と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`secure_compare`、`短絡評価`、`定数時間比較`など、技術用語が正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

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

`is_a?(String)`によるガード節の追加がなぜ`NoMethodError`を防ぎ、`401 Unauthorized`レスポンスに繋がるのか、その仕組みが技術的に正確かつ論理的に解説されています。

事実の突合 ⚠ WARNING

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

「設計判断」セクションの内容は、PRに明記されていないものの、コード変更から論理的に導かれる妥当な解説です。しかし、厳密にはPR情報に基づかない推測が含まれるためWARNINGとします。

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

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

PR番号(#57485)とIssue番号(#57484)が正確に記載・リンクされています。

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

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

記事のタイトルはPRの主題「Reject malformed Mailgun signatures」をより具体的に分かりやすく表現しており、内容と完全に一致しています。

外部知識の正確性 ✓ PASS

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

記事には、バージョン情報やリリース日程など、PRにない外部知識の捏造は見られません。「設計判断」での技術的背景の言及は、文脈説明として妥当な範囲です。

時間表現の正確性 ✓ PASS

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

「既に」や「将来」といった、誤解を招く可能性のある時間表現は使用されておらず、事実を客観的に記述しています。