前端知库

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 给的例子是这样的:

JavaScript
const untrustedString = 'abc <script>alert(1)<' + '/script> def';
someElement.setHTML(untrustedString);
console.log(someElement.innerHTML); // abc def

不传配置时用的是默认配置。它比“去掉 XSS”还要严,MDN 写明默认配置会删掉可能用于点击劫持、伪装的元素,也会删掉注释和 data-* 属性。

需要精确控制时,传一个 sanitizer 选项。下面是一个白名单配置,只允许几个排版标签,<b> 去掉标签但保留里面的文字:

JavaScript
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 写进了白名单:

JavaScript
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 还不支持,现在比较稳妥的写法是:

JavaScript
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() 会保证配置始终合法。