DOMPurify 是一個只在 DOM 上運作的 XSS sanitizer,輸入一段不可信的 HTML 字串,回傳移除危險標籤與屬性後的字串,最常見的呼叫只有一行:const clean = DOMPurify.sanitize(dirty);。官方 README 對它的定位是「DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.」,也就是專門處理 HTML、MathML 與 SVG 的淨化工具。
想看在 React 裡怎麼把 HTML 字串渲染出來,可前往 React 如何正確渲染 HTML 字串?dangerouslySetInnerHTML 用法與範例 觀看;想先了解 XSS 有哪幾種類型,可前往 XSS 跨站腳本攻擊三大類型與現代化防禦實作要點 觀看。本文只講 DOMPurify 本身:它做什麼、怎麼設定、哪些情況不能只靠它。
DOMPurify 做什麼:淨化 HTML,不是跳脫文字
使用者輸入如果只是純文字,用 textContent 輸出或讓框架自動跳脫就夠了。但留言編輯器、富文本內容、從 API 取得的 HTML 片段這類資料,必須保留 <b>、<a>、<ul> 等標籤,跳脫會讓格式全部消失。OWASP 的 XSS Prevention Cheat Sheet 對這種情況的說法是:輸出編碼會防住 XSS,但會破壞應用程式原本要的功能,此時應該使用 HTML Sanitization,並且寫明「OWASP recommends DOMPurify for HTML Sanitization.」
DOMPurify 的做法是借用瀏覽器自己的解析器:README 說明它「use the technologies the browser provides and turn them into an XSS filter」,瀏覽器越快,DOMPurify 也越快。所以它處理的是解析後的 DOM 樹,不是用正規表示式去比對字串。
官方範例的淨化結果
README 的「Some purification samples please?」列出幾個例子,註解寫的是淨化後的結果:
DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // becomes <img src="x">
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
DOMPurify.sanitize('<p>abc<iframe//src=jAva	script:alert(3)>def</p>'); // becomes <p>abc</p>
事件屬性 onerror、onload 被移除,<iframe> 連同內容整個被拿掉,其餘結構保留。這些輸出是 README 註解標示的結果,不同版本的輸出可能不同,實際以你安裝的版本為準。
基本用法
在瀏覽器引入
README 提供兩種引入方式:直接用 <script> 載入 dist/purify.js(未壓縮)或 dist/purify.min.js(壓縮的正式版),之後就能呼叫 DOMPurify.sanitize()。使用 npm 與打包工具時,安裝與匯入如下:
- 安裝套件。
npm install dompurify
- 匯入後呼叫
sanitize。
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize('<b>hello there</b>');
README 也說明,淨化後的字串可以用 innerHTML 寫進 DOM 元素,或用 document.write() 寫進文件,這部分由開發者決定。
搭配 innerHTML 的標準流程
const dirty = '<p>留言內容</p><img src=x onerror=alert(1)>';
document.getElementById('comment').innerHTML = DOMPurify.sanitize(dirty);
MDN 把 innerHTML 歸類為注入點(injection sink):輸入如果來自攻擊者,就可能成為 XSS 的途徑。所以不可信的字串在交給它之前,要先經過淨化。
下圖用 purify.js 載入 DOMPurify 3.4.17,把含 onerror 的 <img> 與 <script> 淨化後再寫入 innerHTML:結果只剩 <img src="x">,onerror 沒有執行。圖中也列出 RETURN_TRUSTED_TYPE: true 回傳 TrustedHTML 物件,以及後文 Trusted Types 範例的 policy.createHTML 回傳結果。

Chrome 158、Windows 11 無頭模式載入 DOMPurify 3.4.17 的執行畫面。
在 Node.js 以 jsdom 29.1.1 執行同樣的設定,前面三個官方範例、ALLOWED_TAGS: ['b'] 與 USE_PROFILES: { html: true } 的輸出都與本文說明一致,例如 USE_PROFILES: { html: true } 會移除 <svg> 與 <script>,保留 <p>、<b>、<q style> 與淨化後的 <img src="x">。
下圖是在本機 Chrome 無頭模式對照兩種寫入方式:同一段含 onerror 的 <img> 字串,用 innerHTML 直接寫入時事件處理函式會執行;用瀏覽器內建的 Element.setHTML()(MDN 稱它是 innerHTML 的 XSS 安全替代方案)寫入時則沒有執行。

Chrome 158、Windows 11 無頭模式的執行畫面,測試字串只會把標記寫入 window.__hit,不含實際攻擊行為;setHTML 寫入後該元素的 innerHTML 為空字串。
設定:只留你要的標籤與屬性
預設情況下,DOMPurify 同時允許 HTML、SVG 與 MathML。README 的設定範例如下:
// 只允許 <b>,最嚴格
const clean1 = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b'] });
// 只允許 <b> 與 <q>,且可帶 style 屬性
const clean2 = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'q'],
ALLOWED_ATTR: ['style'],
});
// 只允許安全的 HTML 元素,不含 SVG 與 MathML
const clean3 = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
README 對 USE_PROFILES 有一句明確提醒:它會覆蓋 ALLOWED_TAGS,兩者不要一起使用。富文本只需要一般 HTML 時,USE_PROFILES: { html: true } 是最省事的寫法;要更嚴格再改用 ALLOWED_TAGS 與 ALLOWED_ATTR 白名單。
持久設定與 Hook
不想每次呼叫都傳設定,可以用 DOMPurify.setConfig。README 說明這個設定會持續到下一次 setConfig 或 clearConfig,而且只有一份設定生效:設定之後,傳給 sanitize 的額外設定參數都會被忽略。
需要客製行為時可以用 DOMPurify.addHook 掛函式,可掛的時機包含 beforeSanitizeElements、uponSanitizeElement、afterSanitizeElements、beforeSanitizeAttributes、uponSanitizeAttribute、afterSanitizeAttributes 等。
伺服器端使用:需要 DOM,而且要用新版 jsdom
DOMPurify 依賴 DOM,所以在 Node.js 要先提供一個 DOM。README 的做法是搭配 jsdom:
- 安裝兩個套件。
npm install dompurify
npm install jsdom
- 用 jsdom 的 window 建立 DOMPurify 實例。
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const clean = DOMPurify.sanitize('<b>hello there</b>');
README 特別強調要用最新版 jsdom,原因是舊版 jsdom 本身有漏洞,即使 DOMPurify 運作完全正確也可能產生 XSS。它同時指出 happy-dom「are not considered safe」,目前不建議和 DOMPurify 搭配。遇到環境問題時,README 也推薦第三方的 isomorphic-dompurify,但那是另一個專案,使用前請自行評估。
DOMPurify 的限制:不要把它當成萬能開關
1. 淨化之後不要再改內容
README 的警告是:如果先淨化、再修改 HTML,可能輕易讓淨化的效果失效。把淨化後的字串交給其他函式庫時,也要確認那個函式庫不會自行改動 HTML。OWASP 也有相同提醒。所以順序應該是「所有字串處理都做完,最後一步才呼叫 sanitize,然後直接寫入 DOM」。
2. 必須定期更新
OWASP 寫明:You must regularly patch DOMPurify,因為瀏覽器行為會變,繞過手法也持續被發現。把 DOMPurify 鎖在舊版不更新,等於放棄這道防線;README 也把 mutation XSS、namespace confusion、DOM clobbering 等攻擊類型列在「Attack Classes & Bypass History」wiki 中追蹤。
3. 舊瀏覽器上它什麼都不做
README 說明在 Internet Explorer 等舊瀏覽器上,DOMPurify 只會原樣回傳你給它的字串,並提供 isSupported 屬性讓你判斷它能不能運作。v2.5.9 是最後一個支援 MSIE 的版本,需要相容 MSIE 的安全更新要使用 2.x 分支。判斷不支援時,要自己決定退路,例如不渲染該段 HTML。
4. DOMPurify.removed 不能拿來做安全判斷
淨化後可以讀 DOMPurify.removed 看哪些元素與屬性被移除,但 README 明確說不要用這個屬性做任何安全相關的判斷,它只是給開發者看的輔助資訊。
5. Shadow DOM 只處理 open shadow root
DOMPurify 支援 Shadow DOM,但 README 說明只涵蓋 open shadow root;closed shadow root 無法被腳本讀取,所以 DOMPurify 看不到也無法淨化其中內容。不可信的 HTML 請以字串形式交給 DOMPurify。
6. 關閉 DOM Clobbering 防護要小心
SANITIZE_DOM 預設為 true,用來防止 DOM Clobbering。README 註明設成 false 要「handle with care」,因為會帶來小幅的 XSS 風險,沒有明確理由不要關。
搭配 Trusted Types:把淨化變成瀏覽器強制的規則
Trusted Types 是讓瀏覽器強制「HTML sink 只能接收經過政策處理的值」的機制。MDN 說明,HTML sink 的轉換函式通常會做淨化,例如使用 DOMPurify 這類函式庫;Trusted Types API 本身不提供淨化功能。DOMPurify README 的搭配方式是:
window.trustedTypes.createPolicy('my-organization', {
createHTML: (input) =>
DOMPurify.sanitize(input, { TRUSTED_TYPES_POLICY: null }),
});
README 說明,sanitize 在支援 Trusted Types 的環境下,把 RETURN_TRUSTED_TYPE 設為 true 會嘗試回傳 TrustedHTML 值而不是字串;若在自己的 policy 的 createHTML 裡呼叫 DOMPurify,要傳 TRUSTED_TYPES_POLICY: null,避免 DOMPurify 自己再建立名為 dompurify 的內部 policy 而與嚴格的 CSP 衝突。
在 React 裡,官方文件也說明網站啟用 Trusted Types 時,可以把安全政策產生的 TrustedHTML 當成 __html 傳入,React 不會先轉成字串,瀏覽器才能驗證。CSP 的基礎觀念可前往 保護網頁免受攻擊:了解CSP(內容安全策略) 觀看。
瀏覽器內建的 setHTML:未來可能的替代方案
MDN 說明 Element.setHTML() 會解析並淨化字串,一律移除 <script>、<frame>、<iframe>、<embed>、<object>、<use> 與事件處理屬性,建議在瀏覽器支援時,用它取代 innerHTML 處理使用者提供的 HTML。是否能在你的目標瀏覽器上使用,要查 MDN 的相容性表;不支援的環境仍然需要 DOMPurify 這類函式庫。
Q&A
Q1:用了 DOMPurify 還需要 CSP 嗎?
需要。OWASP 的說法是 CSP 應當作額外的防禦層(縱深防禦),輸出編碼與 HTML Sanitization 才是主要做法,兩者是搭配關係,不是擇一。Cookie 的 HttpOnly 也是另一層,可前往 HTTPOnly Cookie 怎麼設定?新手工程師常見 XSS 防禦實作 觀看。
Q2:我只需要純文字,也要用 DOMPurify 嗎?
不需要。純文字用 textContent 或框架預設的跳脫就能處理,DOMPurify 是給「必須保留部分 HTML 標籤」的情境。
Q3:DOMPurify.sanitize 回傳的是字串還是 DOM?
預設回傳字串。README 提到 RETURN_DOM 與 RETURN_DOM_FRAGMENT 設定可改變回傳型別,在 Trusted Types 環境下加 RETURN_TRUSTED_TYPE: true 則會嘗試回傳 TrustedHTML。
總結
DOMPurify 是處理「必須保留 HTML 的不可信字串」的淨化工具:最後一步呼叫 DOMPurify.sanitize,設定只留需要的標籤,不在淨化後修改內容,並定期更新版本。它是縱深防禦的其中一層,不是唯一一層,應和 CSP、Trusted Types、HttpOnly Cookie 一起使用。
發佈留言