Action TextのMarkdownリンク生成にURIスキーム検証を追加
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_representation は MarkdownConversion.markdown_link を呼び出す際にURIスキームの検証を行っていませんでした。
さらに、MarkdownConversion 自身はアンカーリンク生成時に allowed_uri? チェックを行っていたにもかかわらず、画像リンク生成のパスにはその検証が存在せず、Markdownパス内部でも処理が一貫していませんでした。この結果、<action-text-attachment url="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.rb の ActiveStorage::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 は  として出力されることが確認されています。一方、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パイプラインにも適用しています。