カスタム添付コンテンツの過剰サニタイズを修正するDOMPurify設定の統一
カスタム添付コンテンツ(メンション、カードなど)が過剰にサニタイズされていた問題を、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の永続設定機能を活用してサニタイズ設定の一元管理を実現し、エディタ本体と添付コンテンツで設定が乖離することによるバグを構造的に排除しています。サニタイズ設定の責務を呼び出し側から初期化フローへ移動させることで、将来的に添付コンテンツの種類が増えた場合でも、設定漏れによる過剰サニタイズが再発しにくい設計になりました。