Action Mailbox Mandrillインgressで署名ヘッダー欠落時にNoMethodErrorが発生する問題を修正

rails/rails

Action MailboxのMandrillインgressにおいて、X-Mandrill-Signatureヘッダーが欠落したリクエストがNoMethodErrorを引き起こしていた問題を修正しました。署名の存在チェックを追加することで、ヘッダー未送信のリクエストも偽造署名と同様に401 Unauthorizedで確実に拒否されるようになります。

背景

Mandrillインgressの認証ロジックは、リクエストの正当性をX-Mandrill-Signatureヘッダーと期待されるHMACの比較によって検証しています。しかし、このヘッダーが存在しない場合に問題が発生していました。

authenticated?メソッドはActiveSupport::SecurityUtils.secure_compareを呼び出す前に署名の存在チェックを行っておらず、ヘッダーが欠落しているとgiven_signaturenilになります。secure_compareは内部で引数の.bytesizeを呼び出すため、nilが渡された際に以下のエラーが発生していました。

NoMethodError: undefined method `bytesize' for nil
    activesupport/lib/active_support/security_utils.rb:34:in `secure_compare'
    actionmailbox/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb:69:in `authenticated?'

この挙動は#57329として報告されており、期待される動作(401 Unauthorizedを返す)と実際の動作(例外の発生)の乖離が問題の本質です。

技術的な変更

修正はauthenticated?メソッドへの1行の変更で完結しており、署名の存在確認を比較処理の前置条件として追加しています。

変更前:

def authenticated?
  ActiveSupport::SecurityUtils.secure_compare given_signature, expected_signature
end

変更後:

def authenticated?
  given_signature.present? && ActiveSupport::SecurityUtils.secure_compare(given_signature, expected_signature)
end

given_signature.present? によるガード節が追加され、nilや空文字列の場合は&&の短絡評価によりsecure_compareが呼ばれることなくfalseを返します。これにより、署名欠落のリクエストは認証失敗として扱われ、コントローラーが401 Unauthorizedを返す通常のフローに乗ります。

テスト側には、署名ヘッダーなしでリクエストを送信するリグレッションテストが追加されています。

test "rejecting an inbound email from Mandrill without a signature" do
  assert_no_difference -> { ActionMailbox::InboundEmail.count } do
    post rails_mandrill_inbound_emails_url, params: { mandrill_events: @events }
  end

  assert_response :unauthorized
end

このテストは「署名なしのリクエストでInboundEmailレコードが作成されないこと」と「レスポンスが401であること」の両方をアサートしており、再発防止の観点から適切なカバレッジとなっています。

設計判断

present?による存在チェックをsecure_compareの呼び出し前に置く という設計が選ばれています。

nilガードとしてgiven_signature && ...ではなくgiven_signature.present? && ...を使用している点が注目されます。present?nilだけでなく空文字列("")もfalsy扱いにするため、空の署名ヘッダーが送信された場合にも同様に認証を拒否できます。空の署名をsecure_compareに渡した場合は例外は発生しないものの、認証通過の余地を残さないという一貫した防御的実装になっています。

また、secure_compareの呼び出しに括弧を追加する形で引数リストが明示化されており、コードの読みやすさも改善されています。

まとめ

本修正は1行の変更ながら、例外による500エラーを意図した401エラーに切り替えるセキュリティ上重要な修正です。present?を使った防御的な存在チェックにより、署名ヘッダーの欠落・空文字・nilのいずれのケースでも認証フローが正しく機能するようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
3ed87a09

この記事は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リンク記法の正確性

ファイル名付きシンタックスハイライト(言語:ファイルパス)やGitHubのPR/Issueリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

専門知識を持つエンジニアを対象としており、専門用語の解説を省略することで、冗長にならず要点を的確に伝えています。

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

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

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

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロックは、提供されたDiff情報と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Action Mailbox、Mandrill ingress、secure_compareなど、文脈に応じた技術用語が正確に使用されています。

説明の技術的正確性 ✓ PASS

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

ヘッダー欠落時のNoMethodErrorの発生メカニズムや、`present?`による修正がもたらす効果について、技術的に正確かつ論理的に説明されています。

事実の突合 ✓ PASS

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

記事内のすべての主張はPRのDescriptionやDiffのコード内容で裏付けられており、ハルシネーション(捏造)は見られません。

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

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

PR番号(#57330)とIssue番号(#57329)が正確に記載されています。

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

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

記事のタイトルはPRの主題「Handle missing Mandrill ingress signatures」をより具体的に説明しており、内容と完全に一致しています。

外部知識の正確性 ✓ PASS

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

記事の内容はすべて提供されたPR情報に基づいており、バージョンサポート状況などの外部知識の追記はありません。

時間表現の正確性 ✓ PASS

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

問題の発生(過去形)と修正内容(現在形)の時間表現が正確に使い分けられており、PRの内容と一致しています。