Sanitizer API 的 setHTML() 和 DOMPurify 有什么区别?
setHTML() 是浏览器内置的安全版 innerHTML:插入 HTML 时按配置删掉标签和属性,并且无论配置怎么写,都会去掉 script、iframe 和事件属性。DOMPurify 是 JavaScript 库,默认返回净化后的字符串,兼容面更广。截至 2026 年 10 月,Chrome 146 和 Firefox 148 起支持 setHTML(),Safari 还不支持,所以需要特性检测加 DOMPurify 兜底。
简答
setHTML()是浏览器内置的安全版innerHTML。el.setHTML(userHtml)会把 HTML 解析进元素,同时删掉不允许的标签和属性,<script>这类东西进不去。和 DOMPurify 最大的区别在两点。第一,
setHTML()有一条兜底规则:不管传什么配置,<script>、<iframe>、<object>、<embed>、<frame>、<use>和onclick这类事件属性都会被删掉,配置写错也不会放出 XSS。第二,DOMPurify 是一个 JavaScript 库,默认返回净化后的字符串,再由你赋给innerHTML;setHTML()是浏览器原生方法,净化和插入一步完成。它适合富文本评论、Markdown 转出的 HTML 这类必须渲染用户 HTML 的场景,用
elements、attributes这些配置限定允许的内容。现状要说清楚:MDN 标为有限可用,按兼容数据,Chrome 146、Firefox 148 起支持,Safari 还没有实现。所以现在的做法是像 MDN 示例那样检测
'Sanitizer' in window,不支持时退回 DOMPurify。
setHTML() 怎么用
最简单的用法是不传配置,直接替换 innerHTML。MDN 给的例子是这样的:
const untrustedString = 'abc <script>alert(1)<' + '/script> def';
someElement.setHTML(untrustedString);
console.log(someElement.innerHTML); // abc def
不传配置时用的是默认配置。它比“去掉 XSS”还要严,MDN 写明默认配置会删掉可能用于点击劫持、伪装的元素,也会删掉注释和 data-* 属性。
需要精确控制时,传一个 sanitizer 选项。下面是一个白名单配置,只允许几个排版标签,<b> 去掉标签但保留里面的文字:
const sanitizer = new Sanitizer({
elements: ['p', 'strong', 'em', 'a', 'ul', 'li'],
replaceWithChildrenElements: ['b'],
attributes: ['href'],
});
comment.setHTML(userHtml, { sanitizer });
配置分两种写法。白名单用 elements、attributes,没列出的全删;黑名单用 removeElements、removeAttributes,没列出的全留。同一类不能两种混用,比如同时写 elements 和 removeElements,setHTML() 会抛 TypeError。MDN 认为白名单更安全。
为什么说配置写错也不会放出 XSS
这是 setHTML() 的核心设计。看下面这个配置,故意把 script 写进了白名单:
const sanitizer = new Sanitizer({ elements: ['div', 'p', 'button', 'script'] });
target.setHTML(unsanitizedString, { sanitizer });
结果 <script> 还是被删了。MDN 说明 setHTML() 总会去掉 XSS 不安全的元素和属性,即使配置允许它们。DOMPurify 没有这条写死的兜底,安全性取决于你传的选项。
那确实要保留某个危险属性怎么办?要换成 setHTMLUnsafe(),它按配置原样执行,不做兜底,不传配置时和 innerHTML 差不多。名字里带 Unsafe,就是为了让审查代码的人一眼看到风险点。
和 DOMPurify、Trusted Types 怎样配合
DOMPurify 默认输出字符串,开启 RETURN_TRUSTED_TYPE 时会尝试返回 TrustedHTML。它的优势是兼容面广,README 写明支持 Safari 10+、Firefox、Chrome、Edge 等现代浏览器。
和 Trusted Types 的关系是这样的:开启 Trusted Types 后,innerHTML 只收 TrustedHTML,所以 DOMPurify 要放在策略里使用,见《Trusted Types 会自动过滤所有危险 HTML 吗?》。setHTML() 不经过 Trusted Types,因为它一定会去掉 XSS 内容,MDN 说这类调用没有需要审查的东西。setHTMLUnsafe() 则仍受 Trusted Types 约束。
MDN 也说,用安全方法时一般不需要 DOMPurify。但考虑到 Safari 还不支持,现在比较稳妥的写法是:
if ('Sanitizer' in window) {
container.setHTML(userHtml);
} else {
container.innerHTML = DOMPurify.sanitize(userHtml);
}
不管用哪种,净化只是一层防护。显示纯文本时用 textContent,链接协议限制、CSP 等其他措施见《XSS 有哪几种?前端怎样防范?》。
常见追问
Chrome 很早就有 Sanitizer API,为什么现在才说能用?
兼容数据里注明,Chrome 105 到 118 支持过一个规范差别很大的旧版本。现在的
setHTML()定义在 WHATWG HTML 标准里,兼容数据记的支持起点是 Chrome 146,所以早年文章里的写法不能照搬,要以 MDN 当前文档为准。
有没有只解析不插入的办法?
有,
Document.parseHTML()。它按同样的规则净化,然后返回一个新的Document,适合先检查结果再决定怎么用。对应的不安全版本是Document.parseHTMLUnsafe()。
同一份配置反复用,传对象和传 Sanitizer 有区别吗?
MDN 说重复使用时,
Sanitizer实例预计比每次传配置对象更高效。另外Sanitizer的方法如allowElement()、removeElement()会保证配置始终合法。