Action TextのMarkdownリンク生成にURIスキーム検証を追加

rails/rails

Action TextのMarkdownパイプラインで、危険なURIスキームを含む <action-text-attachment> 要素が未検証のままMarkdownリンクとして出力されていた脆弱性を修正しました。MarkdownConversion.markdown_link にURIスキーム検証を組み込み、HTMLレンダリングパイプラインとの一貫性を確保しています。

背景

HTMLパイプラインとMarkdownパイプラインの間に、URIスキーム検証の扱いに一貫性がありませんでした。HTMLレンダリングパイプラインでは SafeListSanitizer<img src> 属性から javascript:data:text/html などの危険なURIスキームを除去します。一方、Markdownパイプラインの RemoteImage#attachable_markdown_representationMarkdownConversion.markdown_link を呼び出す際にURIスキームの検証を行っていませんでした。

さらに、MarkdownConversion 自身はアンカーリンク生成時に allowed_uri? チェックを行っていたにもかかわらず、画像リンク生成のパスにはその検証が存在せず、Markdownパス内部でも処理が一貫していませんでした。この結果、<action-text-attachment url="data:text/html,PAYLOAD"> のような細工された要素が ![Image](data:text/html,PAYLOAD) というMarkdown出力を生成できる状態にありました。

技術的な変更

MarkdownConversion.markdown_link にURIスキーム検証とimage出力の統一が行われ、呼び出し側のコードが整理されました。

markdown_link メソッドのシグネチャ変更(markdown_conversion.rb):

変更前は image: オプションを持たず、URIスキームを検証しませんでした。

def markdown_link(title, url)
  "[#{escape_markdown_text(title)}](#{encode_href(url)})"
end

変更後は image: キーワード引数を追加し、Rails::HTML::Sanitizer.allowed_uri? によるスキーム検証を行います。スキームが許可されていない場合は \[title\] のようにエスケープされた角括弧付きのテキストを返します。

def markdown_link(title, url, image: false)
  if Rails::HTML::Sanitizer.allowed_uri?(url)
    "#{"!" if image}[#{escape_markdown_text(title)}](#{encode_href(url)})"
  else
    "\\[#{escape_markdown_text(title)}\\]"
  end
end

呼び出し側の変更(remote_image.rb および engine.rb):

RemoteImage#attachable_markdown_representation では、image: プレフィックスを呼び出し元で組み立てる代わりに markdown_link へ委譲するようになりました。

# 変更前
"!#{MarkdownConversion.markdown_link(caption || "Image", url)}"

# 変更後
MarkdownConversion.markdown_link(caption || "Image", url, image: true)

engine.rbActiveStorage::Blob 用パスでも同様に image? の条件分岐が markdown_link への image: 引数渡しに統合されています。また、リンクが生成されないケース(attachment_links: false 時など)のフォールバック出力も [caption] から \[caption\] へ変更され、エスケープされた角括弧を使用するようになりました。

検証の境界値(data: URIの扱い):

data:image/png;base64,... のような画像データURIは Rails::HTML::Sanitizer.allowed_uri? によって許可されるため、引き続き画像リンクとして出力されます。テストにより data:image/png;base64,abc![photo](data:image/png;base64,abc) として出力されることが確認されています。一方、data:text/html,... は許可されず \[Image\] に変換されます。

設計判断

URIスキーム検証を markdown_link メソッド内に集約する設計 が採用されました。

PRではアンカーリンク生成時には既に allowed_uri? チェックが存在していたことが言及されており、その検証ロジックを markdown_link メソッドに統合することで、すべての呼び出しパスに対して一貫したセキュリティ境界を設けています。各呼び出し元が個別に検証を行う分散パターンではなく、リンク生成の唯一の入口で検証を完結させるアプローチです。

image: フラグを呼び出し元で ! プレフィックスとして組み立てていた処理を markdown_link 内部に移したことも、この一元化の一環です。これにより将来の呼び出し元が image: フラグを渡すだけで、URIスキーム検証を含む正しい出力が得られるようになります。

なお、PRでは ActiveStorage::Blob パスが url_for(self) を使用して安全なシステムURLを生成するため直接の影響はないことが明示されていますが、今回の修正は全呼び出しパスに対する多層防御(defense-in-depth)として機能します。

まとめ

本PRは、Markdownパイプライン内のURIスキーム検証漏れを markdown_link への一元化で解消した変更です。image: オプションの統合と合わせて呼び出し元の責務を削減しながら、HTMLパイプラインで既に行われていた安全性の保証をMarkdownパイプラインにも適用しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
0e76c2d5

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

ファイル名付きシンタックスハイライト(```ruby:ファイルパス)とPR番号のリンク記法([PR #56909](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Action Textの内部実装に関する詳細な解説であり、専門知識を持つエンジニアという対象読者に完全に適合しています。

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

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

各セクションが総論→各論で構成され、各パラグラフはトピックセンテンスで始まるなど、パラグラフ・ライティングの原則を遵守しており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは、提供されたDiff情報と完全に一致しています。メソッドのシグネチャ変更や呼び出し側の修正が正確に引用されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「SafeListSanitizer」「MarkdownConversion.markdown_link」「allowed_uri?」などの技術用語が、PRの文脈通りに正確に使用されています。

説明の技術的正確性 ✓ PASS

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

HTMLパイプラインとMarkdownパイプラインの不整合や、URIスキーム検証のロジックに関する説明は、PR DescriptionやDiffの内容に基づいた技術的に正確なものです。

事実の突合 ✓ PASS

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

記事内のすべての主張(脆弱性の内容、修正アプローチ、設計判断など)は、PRのTitle、Description、Diffの内容によって裏付けられており、ハルシネーションは検出されませんでした。

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

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

PR番号(#56909)が正確に記載されています。

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

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

記事のタイトル「Action TextのMarkdownリンク生成にURIスキーム検証を追加」は、PRのタイトル「Validate URI scheme in Action Text markdown link conversion」の内容を的確に要約しています。

外部知識の正確性 ✓ PASS

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

記事の内容は提供されたPR情報に限定されており、バージョン情報やリリース予定など、PR外の知識の追記は見られませんでした。

時間表現の正確性 ✓ PASS

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

「〜でした」「〜できる状態にありました」といった過去の状態を示す表現が、PR内の「previously」「already」といった記述と一致しており、時間表現は正確です。