Mailgun Action Mailboxで不正なタイムスタンプを適切に拒否する

rails/rails

Mailgun Action Mailboxのイングレスにおいて、不正な形式のタイムスタンプが送信された場合にArgumentErrorでサーバーエラーが発生していた問題が修正されました。これにより、不正リクエストは意図通り401 Unauthorizedとして拒否されるようになります。

背景

Authenticatorクラスの初期化処理に、不正なタイムスタンプがArgumentErrorを引き起こすというセキュリティ上の問題がありました。#57315として報告されたこの問題では、認証チェックが完了する前に例外が発生するため、リクエストが401 Unauthorizedではなく500系のサーバーエラーとして処理されてしまっていました。

具体的には、timestampとして"not-a-number"のような文字列を渡すと、Integer(timestamp)ArgumentErrorを送出します。本来であれば認証失敗として扱うべきリクエストが、エラーハンドリングの経路に乗らずにイングレスをエスケープできる状態でした。HMAC署名の検証やタイムスタンプの鮮度チェックに到達する前に例外が発生するため、認証ロジック全体をバイパスしてしまうことが問題の本質です。

技術的な変更

Authenticator#initializeとプライベートメソッドrecent?の2箇所が修正され、タイムスタンプのパースと利用が分離されました。

変更前:

def initialize(key:, timestamp:, token:, signature:)
  @key, @timestamp, @token, @signature = key, Integer(timestamp), token, signature
end

def recent?
  Time.at(timestamp) >= 2.minutes.ago
end

変更後:

def initialize(key:, timestamp:, token:, signature:)
  @key, @timestamp, @token, @signature = key, timestamp.to_s, token, signature
  @parsed_timestamp = Integer(timestamp, exception: false)
end

def recent?
  @parsed_timestamp && Time.at(@parsed_timestamp) >= 2.minutes.ago
end

変更のポイントは2点あります。第一に、@timestampにはHMAC署名の入力として使用する生の文字列を保持するようになりました。expected_signatureメソッドはtimestampを文字列としてHMACの入力に連結するため、この変更は署名検証の動作に影響しません。第二に、Integer(timestamp, exception: false)を使って@parsed_timestampを別途パースします。exception: falseオプションにより、パースに失敗した場合は例外の代わりにnilが返ります。

recent?では@parsed_timestampnilでないことを事前にチェックするため、不正なタイムスタンプの場合はfalseを返し、認証失敗として扱われます。あわせてテストも追加され、不正なタイムスタンプを持つリクエストが401 Unauthorizedを返すことが検証されています。

設計判断

HMAC署名の入力とタイムスタンプのパースを分離する方式が採用されました。

MailgunのHMAC署名は"#{timestamp}#{token}"という文字列を入力として生成されます。署名検証の正確性を保つためには、受け取ったtimestampの生の文字列をそのままHMAC入力に使う必要があります。もしInteger変換後の値を使うと、"1234"" 1234"のような入力が同一視され、署名の検証が緩くなる可能性があります。@timestampを文字列のまま保持することで、この問題を回避しています。

また、Integer(timestamp, exception: false)Integer(timestamp)rescue ArgumentErrorで囲む代替手段と比べて、例外処理のオーバーヘッドなしに失敗をnilで表現できる明快なアプローチです。nilのfalsy性を@parsed_timestamp &&で活用するrecent?の実装も、追加の条件分岐を最小限に抑えた設計といえます。

まとめ

この修正は、タイムスタンプのパース失敗を例外ではなくnilで表現することで、認証フローを一貫してfalseで完結させる変更です。HMAC入力用の生文字列とパース済み整数を分離した設計により、署名検証の正確性を維持しながら不正入力への堅牢性を高めています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
63ebfab3

この記事は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

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

HMAC署名、イングレス、ArgumentErrorといった技術用語が適切に使用されており、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクション、各パラグラフが総論→各論の構造になっており、トピックセンテンスも明確です。1段落1トピックが守られ、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事内で引用されている変更前・変更後のコードは、提供されたDiffの内容と完全に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Integer(..., exception: false)やHMAC署名に関する用語の使用方法が正確で、技術的なニュアンスを正しく伝えています。

説明の技術的正確性 ✓ PASS

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

タイムスタンプのパース失敗がなぜサーバーエラーを引き起こすのか、そして今回の修正でなぜそれが解決されるのかについての説明が技術的に正確かつ論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescriptionやDiff内のコード変更によって裏付けられています。特に「設計判断」セクションは、コードの意図を深く読み解いており秀逸です。

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

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

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

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

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

記事のタイトル「Mailgun Action Mailboxで不正なタイムスタンプを適切に拒否する」は、PRのタイトル「Reject malformed Mailgun ingress timestamps」の内容を的確に日本語で表現しています。

外部知識の正確性 ✓ PASS

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

バージョン情報やリリース予定など、PR情報に基づかない外部知識の追記はなく、信頼性が保たれています。

時間表現の正確性 ✓ PASS

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

「問題があった」「修正された」といった過去の事象として正しく記述されており、時間表現の歪曲はありません。