カスタム添付コンテンツの過剰サニタイズを修正するDOMPurify設定の統一

basecamp/lexxy

カスタム添付コンテンツ(メンション、カードなど)が過剰にサニタイズされていた問題を、DOMPurifyの永続設定機能を活用して解決しました。サニタイズ設定を初期化時に一元管理する仕組みに変更したことで、エディタ本体と添付コンテンツの両方で同一の許可タグセットが適用されるようになります。

背景

これまでの実装では、エディタ本体のサニタイズと添付コンテンツのサニタイズが別々の関数で処理されており、許可タグの設定が統一されていませんでした。sanitize() 関数は allowedElements を引数として受け取りDOMPurifyの設定を都度構築していたのに対し、sanitizeAttachmentContent() はDOMPurifyのデフォルト設定をそのまま使用していました。

この分離した設計がバグの根本原因でした。サーバーサイドでレンダリングされた添付コンテンツは <span><div><img> などのタグを正当に含みますが、DOMPurifyのデフォルト設定はそれらを許可しない場合があります。結果として、本来保持されるべき添付コンテンツのHTML構造が過剰に削除されていました。

技術的な変更

DOMPurifyの 永続設定(Persistent Configuration) 機能を活用し、サニタイズ設定を初期化時に一度だけ設定する方式に変更しました。

src/helpers/sanitization_helper.js では、sanitize() の引数から allowedElements が削除され、新たに setSanitizerConfig() 関数が追加されました。

変更前:

import DOMPurify from "dompurify"
import { buildConfig } from "../config/dom_purify"

export function sanitize(html, allowedElements) {
  return DOMPurify.sanitize(html, buildConfig(allowedElements))
}

// Sanitize HTML for custom attachment content (mentions, cards, etc.).
// Uses DOMPurify defaults to strip XSS vectors (scripts, event handlers)
// while preserving the richer tag set that server-rendered attachment
// content legitimately uses (e.g. <span>, <div>, <img>).
export function sanitizeAttachmentContent(html) {
  return DOMPurify.sanitize(html)
}

変更後:

import { DOMPurify, buildConfig } from "../config/dom_purify"

export function setSanitizerConfig(allowedTags) {
  DOMPurify.clearConfig()
  DOMPurify.setConfig(buildConfig(allowedTags))
}

export function sanitize(html) {
  return DOMPurify.sanitize(html)
}

src/config/dom_purify.js では、DOMPurify インスタンス自体をnamed exportするよう変更されました。これにより、sanitization_helper.js が同一インスタンスを参照でき、setConfig() で設定した内容が sanitize() 呼び出し時にも有効になります。

src/elements/editor.js では、エディタの初期化シーケンスに #configureSanitizer() プライベートメソッドの呼び出しが追加されました。#loadInitialValue() の直前に配置されることで、コンテンツ読み込み前にサニタイズ設定が完了することが保証されています。

#configureSanitizer() {
  setSanitizerConfig(this.#allowedElements)
}

src/nodes/custom_action_text_attachment_node.js では、削除された sanitizeAttachmentContent() の代わりに統一された sanitize() が使用されるようになりました。添付コンテンツも初期化時に設定された許可タグセットでサニタイズされるため、過剰な削除が発生しなくなります。

設計判断

サニタイズ設定の責務をエディタ初期化フローに集約する方針が採用されました。

変更前は sanitize() の呼び出し側が毎回 allowedElements を渡す責務を持っていましたが、この設計では呼び出し元によって設定が異なる可能性があり、今回のバグのような不整合が生じやすい構造でした。変更後は、LexicalEditorElement の初期化時に setSanitizerConfig() を一度だけ呼び出すことで、以降のすべての sanitize() 呼び出しが同一の設定を参照します。

DOMPurifyの setConfig() / clearConfig() APIを活用することで、設定情報をモジュール間で引き回す必要がなくなり、sanitize() のシグネチャがシンプルになっています。clearConfig()setConfig() の前に呼び出す実装は、再初期化時に前回の設定が残留しないことを保証する防御的な設計です。

まとめ

この変更は、DOMPurifyの永続設定機能を活用してサニタイズ設定の一元管理を実現し、エディタ本体と添付コンテンツで設定が乖離することによるバグを構造的に排除しています。サニタイズ設定の責務を呼び出し側から初期化フローへ移動させることで、将来的に添付コンテンツの種類が増えた場合でも、設定漏れによる過剰サニタイズが再発しにくい設計になりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
b53a711c

この記事は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番号のリンク記法([#966](URL))が、ガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

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

DOMPurifyやサニタイズといった専門用語を前提として解説しており、対象読者であるエンジニアに適した技術レベルと表現で書かれています。

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

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

セクション内の構成(総論→各論)、パラグラフのトピックセンテンス先頭配置、1段落1トピックの原則が徹底されており、極めて高い可読性を実現しています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロック(変更前・変更後)は、提供されたDiff情報と完全に一致しており、変更点が正確に示されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「永続設定(Persistent Configuration)」「責務」「シグネチャ」など、技術用語が正確かつ文脈に即して適切に使用されています。

説明の技術的正確性 ✓ PASS

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

サニタイズ設定を一元管理する仕組みへの変更理由と、その結果としてバグが解消されるという因果関係が、Diff内容に基づいて論理的かつ正確に説明されています。

事実の突合 ✓ PASS

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

記事内のすべての主張(問題点、解決策、実装詳細)が、PRのタイトル、Description、Diffの内容によって裏付けられており、ハルシネーションは一切見られません。

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

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

PR番号(#966)、ファイルパス、関数名などの固有名詞がすべて正確に記載されています。

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

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

記事のタイトルはPRの主題「Fix over-sanitization of custom attachment contents」を正確に反映し、かつ解決策(DOMPurify設定の統一)まで含んでおり、秀逸です。

外部知識の正確性 ✓ PASS

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

PR情報に含まれないバージョン情報やリリース日程などの外部知識の追記はなく、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

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

「これまでの実装では」「変更後は」といった時間表現がコードの変更前後を正しく示しており、歪曲や誤解を招く表現はありません。