DOMPurify 上手:XSS 清洗的配置。

DOMPurify 是 cure53 维护的 JavaScript XSS 过滤器,用浏览器原生解析能力清洗 HTML、MathML 与 SVG 标记,Star 数 17422,Apache-2.0 许可。这篇文章梳理它的功能清单、关键配置、最短接入示例与用户实际踩过的坑,帮你判断它适不适合放在你的渲染链路里。

网页只要允许用户提交内容,就有把攻击者构造的标签原样送进页面的风险。评论区、Markdown 渲染、SVG 头像、富文本编辑器,这些位置一旦用 innerHTML 直接写入字符串,藏在 onerror、script 或属性里的脚本就会在别人浏览器里执行。自己写正则过滤标签的做法很难覆盖解析器的各种怪异行为,漏掉一个属性名就算失守。

DOMPurify 是一个 JavaScript 写的 DOM 层 XSS 过滤器:输入可能含恶意标签的 HTML、MathML 或 SVG 字符串,输出清洗后的安全标记,供要渲染用户提交内容的网页使用。

它借浏览器原生的解析能力处理脏字符串,再按允许清单裁掉多余的元素与属性。默认配置就是安全的,代价是你也能通过配置项和钩子把它调得很松。项目始于 2014 年 2 月,当前版本 v3.4.16,仓库地址 https://github.com/cure53/DOMPurify,采用 Apache License 2.0(仓库中另附 MPL-2.0 许可文本),主要语言是 JavaScript,代码里还有 31.7% 的 TypeScript。Star 17422,Fork 861,当前开放 Issue 为 0。

DOMPurify 项目在代码托管平台上的仓库页面

它不做什么

DOMPurify 需要 DOM 环境才能工作,纯 Node.js 里跑不起来。要在服务端用,得自己配 jsdom 这类能提供 DOM 的实现;仓库的自动化测试正是用 jsdom 在 Node v20、v22、v24、v25、v26 上跑的,更低版本官方只说「知道能跑,但不保证」。

旧浏览器上它什么也不做。README 写明了它在 MSIE 及其他遗留浏览器上「simply does nothing」,v2.5.9 是最后一个支持 MSIE 的版本,后续面向 MSIE 的安全更新只在 2.x 分支维护。

清洗完成之后,如果还有别的库改写这段标记,或者把结果换到另一个解析上下文里重新解析,清洗的效果会被抵消。README 把这一点单独拎出来警告,并建议先读它的 Security Goals & Threat Model。

Node 环境还有版本门槛。从 3.3.2 起要求 Node >= 20,用 semver 范围安装的项目在更低版本上会直接装不上,这一点在 Issue 里被用户点名过。

用之前先准备好什么

  • 一个能提供 DOM 的运行环境:现代浏览器,或者 Node 加 jsdom 之类的实现。
  • 可用的目标浏览器范围:Safari 10+、Opera 15+、Edge、Firefox、Chrome,以及其他使用 Blink、Gecko、WebKit 内核的浏览器。
  • 待清洗的输入。可以是脏 HTML 字符串,也可以是已经建好的 Element、DocumentFragment 或 Document 节点。
  • 如果要开 RETURN_TRUSTED_TYPES,浏览器本身得支持 Trusted Types。

主要功能

  • 单次清洗调用。核心入口是 DOMPurify.sanitize(),传进脏字符串,返回清洗后的结果(除非你配置成别的返回形式)。这是它最常用的一条路径。
  • 三种内容类型的 profile 切换。默认允许 HTML、SVG 和 MathML 三类标记。只要处理 HTML 的话,用 { USE_PROFILES: { html: true } } 把范围收窄,减少允许清单面积。
  • 接受字符串或 DOM 节点。sanitize() 也接受 Element、DocumentFragment 和 Document。3.4.14 起,对不是从 HTML 解析器出来的节点做了加固,覆盖经 DOM API 构造或按 XML/XHTML 解析出来的节点,这类节点可能带着保留大小写的属性名,字符串清洗路径看不到它们。
  • 可配置的允许清单与钩子。默认安全,但允许清单可以调整,也可以通过 Hooks 在清洗过程中插入自己的处理逻辑。仓库 README 里单独列了配置、Persistent Configuration 和 Hooks 三节。
  • Trusted Types 集成。通过 RETURN_TRUSTED_TYPES 让 sanitize() 返回 TrustedHTML,配合浏览器的 Trusted Types 策略使用。
  • 就地清洗。IN_PLACE 相关能力用于在原有节点上处理,3.4.13 与 3.4.16 两个版本都针对它与 hooks、raw-text 根节点的组合问题做了修复。
  • 查看被移除的内容。清洗后可以读 DOMPurify.removed 属性,看哪些元素和属性被扔掉了。README 明确说不要拿这个属性做任何安全关键决策。

安装与最短示例

浏览器里直接引构建产物。未压缩版带 source-map:

<script type="text/javascript" src="dist/purify.js"></script>

生产用压缩版:

<script type="text/javascript" src="dist/purify.min.js"></script>

引入之后,一行就能洗:

const clean = DOMPurify.sanitize(dirty);

npm 上的包名是 dompurify,模块化项目里这样引入:

npm install dompurify
import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize('<b>hello there</b>');

只允许 HTML,不允许 SVG 和 MathML:

const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });

关键参数

  • USE_PROFILES:控制允许哪几类标记。默认 HTML、SVG、MathML 全开,只写 { html: true } 就只留 HTML。
  • RETURN_TRUSTED_TYPES:开启后返回 TrustedHTML 对象。仓库 Issue 里提到,接近默认配置但打开这一项时,自定义元素会被全部剥掉。
  • IN_PLACE:就地清洗相关配置。近几个版本围绕它与 hooks、raw-text 根节点、ownerDocument 组合出过若干修复,升级时值得留意版本说明。
  • Hooks:在清洗流程中挂自己的函数。3.4.12 修过自定义元素不触发钩子的问题,以及钩子移除元素时的处理。
  • DOMPurify.removed:只读诊断信息,记录被剥掉的元素与属性,不能拿来做安全判断。

README 里还保留了一节 Removed Configuration,列的是已经移除的配置项。从旧版本迁移过来时,建议先核对这一节,不要照搬老配置。

结果在哪里看

清洗结果就是 sanitize() 的返回值。默认是字符串形式的干净 HTML,你可以自己决定写进 innerHTML,还是走 document.write() 交给 DOM。传节点进去时,结果可能体现在节点的就地修改上。

被丢弃的元素和属性记在 DOMPurify.removed 里,用于排查「为什么这个标签没渲染出来」这类问题。仓库里有 demos/ 和 website/ 两个目录,项目主页 https://cure53.de/purify 上也有可交互的演示页。

实际使用中的坑

  • 自定义元素被全部剥离。在接近默认的配置下(只额外打开 RETURN_TRUSTED_TYPES: true),DOMPurify 会把所有自定义元素清掉,基于自定义元素构建的应用直接不可用。这条 Issue 积累了 55 条评论,目前标记为已解决。
  • sanitize() 返回的不一定是字符串。Chrome 77 时期,它返回的是 TrustedHTML 对象,依赖字符串结果的代码会挂,用户靠补一个 .toString() 绕过。Issue 已解决。
  • Node 版本门槛造成安装失败。3.3.2 起要求 Node >= 20,还在 Node 16.x 上、且依赖 semver 范围自动升级的项目会直接报错。Issue 已解决。
  • 大小写敏感的 SVG 会被转小写。有用户指出,DOMPurify 虽然声称支持 SVG,但标签会被统一转成小写,对大小写敏感的场景不适用。Issue 已解决。

这几条都已关闭,但它们说明一件事:升级小版本前先看 Release 说明。项目每两到三周发一次版本,修复内容里既有安全加固,也有会改变行为边界的调整。

同类项目横向对照

项目适合谁部署方式主要限制什么情况下选它更合适项目地址
DOMPurify要在网页里渲染用户提交的 HTML、SVG 或 MathML,并且希望默认配置就安全的前端与同构渲染场景浏览器引入 dist 目录下的构建产物,或 npm 装包后在 JS 里引入;服务端需自备 jsdom 之类的 DOM 实现依赖 DOM 环境,纯 Node.js 不可直接用;MSIE 等旧浏览器上不生效;清洗后的标记若再被其他库改写会失效清洗对象是准备写进 DOM 的那一段 HTML 片段,且你需要把允许范围细到标签和属性DOMPurify
w3c/trusted-types新项目里能改动所有 innerHTML 赋值点、希望由浏览器层强制约束注入面的团队浏览器 API 与规范文本,需要浏览器实现支持;仓库没有说明安装步骤这是规范仓库,不是可直接安装的过滤库,接入方式与 DOMPurify 不同;具体支持范围未逐一核实你想从源头约束字符串进入 DOM 的路径,而不是清洗一段已经拿到的标记时w3c/trusted-types
AhmedAdelFahim/express-xss-sanitizer用 Express 4.x 或 5.x 写服务端、想在请求入口统一清洗 req.body 等数据作为 Express 中间件接入只覆盖 Express 的请求数据入口,不处理渲染前的 HTML 片段;其余细节未逐一核实清洗位置在 Express 请求管道上,而不是在写进 DOM 之前AhmedAdelFahim/express-xss-sanitizer

如果你要处理的是 Express 的请求体,而不是准备写进 DOM 的 HTML 片段,DOMPurify 的 DOM-only 定位会逼你先接一层 jsdom 或者改架构,这一步比直接挂一个中间件麻烦。它的价值集中在单段标记的清洗上,把它当请求入口的通用过滤器用,是错配。

适合谁

适合负责渲染用户提交内容的前端开发者:你在做评论、论坛、富文本预览、Markdown 渲染或 SVG 头像上传,需要一个默认安全、又能按场景收窄允许范围的过滤器。也适合在服务端做同构渲染、且愿意为它准备 jsdom 环境的团队。

不适合把请求入口当作唯一防线的后端:你要的是在 Express 中间件层面拦掉 req.body 里的脏数据,那是 express-xss-sanitizer 那类项目的活。也不适合还停留在 MSIE 或 Node 16 的项目,前者在 v2.5.9 之后没有安全更新,后者连新版本都装不上。

焚评:这个项目的量化评分

本项目的选题来自 焚.com(一个按公开公式给 GitHub 项目打分的站)。焚评当前总分 10.0 分(满分 10)。下表是各维度的得分:

评分维度得分
热度动量(权重 25%)10.0 / 10
开发活跃(权重 25%)10.0 / 10
社区响应(权重 15%)10.0 / 10
文档质量(权重 15%)10.0 / 10
发布节奏(权重 10%)9.9 / 10
风险控制(权重 10%)10.0 / 10

评分口径、权重与计算方式见焚.com 的评分方法页;数据随 GitHub 指标刷新,具体数值以焚.com 当前页面为准。本文正文为诀.com 独立撰写,评分数据由焚.com 授权引用。

内容核验说明

正文把 DOMPurify 的边界说得比较清楚:需要 DOM 环境、旧浏览器不生效、清洗结果再被改写会失效,还列出 Node 版本门槛和几条已关闭的 Issue 当升级前的提醒。真正有用的是最短接入示例,以及 USE_PROFILES、RETURN_TRUSTED_TYPES、IN_PLACE 这几个配置的取舍。

文中 Star、Fork、版本号、Issue 状态、Node 测试版本等来自本项目在 GitHub 的公开仓库信息,焚评评分与权重引自焚.com,诀.com 均未独立验证;文中没有作者自述的实测或复现记录,示例属于文档级用法,实际效果需自行在目标环境确认。

项目来源与说明

开源项目:cure53(cure53)

本文由诀.com 编辑基于该项目的公开信息独立撰写,属原创解读,不是对项目文档的翻译或转载;文中提到的功能与参数以官方仓库为准,代码与文档版权归原作者所有。

查看项目仓库