Action Mailbox の Ingress コントローラが不正な raw email パラメータを 422 で拒否するように修正
Mailgun・Postmark・SendGrid の Ingress コントローラが、文字列以外の形式で送信された raw email パラメータを 422 Unprocessable Content で拒否するようになりました。これまでは配列などの不正値が内部処理まで到達し、TypeError を発生させていました。
背景
Mailgun・Postmark・SendGrid の各 Ingress コントローラは raw email パラメータを必須としていましたが、その値の型を検証していませんでした。#57494 で報告されたように、RawEmail や body-mime、email パラメータに配列を渡すと、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 として処理されるようになり、エラーレスポンスの一貫性が確保されました。