Action Mailbox の Ingress コントローラが不正な raw email パラメータを 422 で拒否するように修正

rails/rails

Mailgun・Postmark・SendGrid の Ingress コントローラが、文字列以外の形式で送信された raw email パラメータを 422 Unprocessable Content で拒否するようになりました。これまでは配列などの不正値が内部処理まで到達し、TypeError を発生させていました。

背景

Mailgun・Postmark・SendGrid の各 Ingress コントローラは raw email パラメータを必須としていましたが、その値の型を検証していませんでした。#57494 で報告されたように、RawEmailbody-mimeemail パラメータに配列を渡すと、ActionMailbox::InboundEmail.create_and_extract_message_id! 内部の OpenSSL::Digest::SHA1.hexdigest(source)TypeError: no implicit conversion of Array into String を発生させていました。

この挙動は、同じコントローラ内で既に実装されていた受信者メタデータや envelope メタデータの不正値処理と一貫していませんでした。それらのパラメータは不正な形式を受け取ると 422 Unprocessable Content を返す設計になっており、raw email パラメータも同様の扱いが期待されていました。

技術的な変更

各 Ingress コントローラの mail プライベートメソッドに is_a?(String) による型チェックが追加され、不正な値には専用の例外クラスが発生するようになりました。

変更は3つのコントローラで同じパターンを踏んでいます。まず、各コントローラに MalformedEmailError という StandardError のサブクラスが追加されました。

class MalformedEmailError < StandardError
  def initialize(message = "Malformed Postmark raw email")
    super
  end
end

次に、mail メソッド内で params.require(...) が返した値に対して型チェックを行い、文字列でない場合は MalformedEmailError を発生させます。

# 変更前
def mail
  params.require("RawEmail").tap do |raw_email|
    raw_email.prepend("X-Original-To: ", original_recipient, "\n") if params.key?("OriginalRecipient")
  end
end

# 変更後
def mail
  params.require("RawEmail").tap do |raw_email|
    raise MalformedEmailError unless raw_email.is_a?(String)

    raw_email.prepend("X-Original-To: ", original_recipient, "\n") if params.key?("OriginalRecipient")
  end
end

最後に、create アクションの rescue 節に MalformedEmailError が追加されています。Postmark の場合は以下のように変更されました。

# 変更前
rescue ActionController::ParameterMissing, MalformedOriginalRecipientError => error

# 変更後
rescue ActionController::ParameterMissing, MalformedEmailError, MalformedOriginalRecipientError => error

Mailgun・SendGrid でも同様のパターンが適用されています。チェック対象のパラメータ名はそれぞれ body-mime(Mailgun)、RawEmail(Postmark)、email(SendGrid)です。

設計判断

各コントローラに既存の MalformedRecipientError / MalformedOriginalRecipientError / MalformedEnvelopeError と同じ設計で MalformedEmailError が追加されました。

このアプローチはエラーの種類ごとに専用の例外クラスを持つという既存の設計を踏襲しています。共通基底クラスへのまとめや rescue ActionMailbox::Error のような汎用的な捕捉ではなく、コントローラごとにスコープを持つ内部クラスとして定義することで、エラーの意味とその処理箇所が明確になっています。

また、params.require(...) が返した後に is_a?(String) でチェックする順序が選ばれています。これにより、パラメータ自体が欠如しているケース(ActionController::ParameterMissing)と、パラメータは存在するが型が不正なケースを分けて扱えます。テストコードでは [ file_fixture("../files/welcome.eml").read ] のように実際のメールコンテンツを配列に包んだ値を使い、現実的な誤用パターンを検証しています。

まとめ

本PRは、raw email パラメータの型チェックを欠いていた3つの Ingress コントローラを、既存の不正値処理パターンと一貫した設計に揃えた修正です。TypeError という内部エラーとして露出していた問題が、コントローラ層で適切に 422 Unprocessable Content として処理されるようになり、エラーレスポンスの一貫性が確保されました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
2c42f198

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→セクション群(各論)→まとめ(結論)の構成が明確です。背景、技術的変更、設計判断といった必須・任意要素がすべて含まれており、理想的な構成です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライトの形式(```ruby:ファイルパス)およびGitHubのPR/Issueリンク記法([#123](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Ingressコントローラ、TypeError、422レスポンスコードなどの用語が前提知識として扱われており、専門のエンジニアという対象読者に適合しています。

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

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

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

Diff内容との照合 ✓ PASS

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

記事内のコード引用(MalformedEmailErrorの定義、mailメソッドの変更、rescue節の変更)は、提供されたDiffの内容と正確に一致しています。ファイルパスも正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Ingressコントローラ」「422 Unprocessable Content」「TypeError」「StandardError」などの技術用語が文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

「配列を渡すとTypeErrorが発生する」という問題、「is_a?(String)で型チェックを追加する」という解決策、「422 Unprocessable Contentを返すようになる」という結果、すべての説明が技術的に正確で、PRの内容と一致しています。

事実の突合 ✓ PASS

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

記事内のすべての主張(問題の背景、変更内容、設計判断)は、PRのDescriptionやDiff内のコードによって裏付けられています。ハルシネーションは検出されませんでした。

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

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

PR番号(#57495)、Issue番号(#57494)、HTTPステータスコード(422)など、記事中の数値や固有名詞はすべて正確です。

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

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

記事のタイトルは、PRのタイトル「Reject malformed Action Mailbox raw email params」の内容を日本語で正確に要約しており、主題が一致しています。

外部知識の正確性 ✓ PASS

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

記事はPRで提供された情報に限定されており、バージョンのサポート状況やリリース日程など、PR外の不確かな外部知識を持ち込んでいません。

時間表現の正確性 ✓ PASS

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

「発生させていました」「するようになりました」といった時間表現は、既存の問題が修正されたというPRの事実関係と正確に一致しています。