指南 · 进阶
版本基线 DOMPurify 3.x。把净化用进真实项目:
addHook定制、Trusted Types 集成、Node/jsdom 与 SSR、协议/URI 控制、安全放行 iframe、与 Markdown 管线协作。
一、hooks:在净化过程里插逻辑
DOMPurify.addHook(entryPoint, cb) 可在净化的各阶段插入回调。最常用的两个入口:
uponSanitizeElement(node, data, config):逐元素触发,data.tagName可用于判断当前元素;afterSanitizeAttributes(node):属性净化后触发,常用于补属性、校验 URL。
例 1:给所有外链 <a> 补 target="_blank" rel="noopener noreferrer"
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.tagName === 'A' && node.getAttribute('href')) {
node.setAttribute('target', '_blank');
node.setAttribute('rel', 'noopener noreferrer');
}
});
const clean = DOMPurify.sanitize(dirty);例 2:在 uponSanitizeAttribute 强制保留某个属性
DOMPurify.addHook('uponSanitizeAttribute', (node, hookEvent, config) => {
// 修改 hookEvent 影响当前属性的处置
if (hookEvent.attrName === 'data-allow-this') {
hookEvent.forceKeepAttr = true; // 即便不在白名单也保留
}
});
forceKeepAttr = true是 README 给出的可变字段,用于精确放行个别属性。
二、hooks 的全局性与清理
addHook 注册在 DOMPurify 实例(通常是全局单例) 上,会影响该实例的每一次 sanitize。所以:
// 用完务必清理,避免污染后续调用
DOMPurify.addHook('afterSanitizeAttributes', myHook);
const clean = DOMPurify.sanitize(dirty);
DOMPurify.removeHook('afterSanitizeAttributes'); // 移除最近一个
// 或 DOMPurify.removeAllHooks(); 清空全部大型应用里若多个模块各自 addHook,钩子会叠加且全局生效,顺序难控、相互干扰。两种隔离思路:
- 多实例:
createDOMPurify(window)造出独立实例,各挂各的钩子(见下一节); - 配对管理:
addHook与removeHook成对出现,或把差异逻辑放进config而非全局钩子。
三、Node.js / SSR:用 jsdom 提供 DOM
DOMPurify 依赖 DOM,浏览器外要先造一个 window:
import { JSDOM } from 'jsdom';
import createDOMPurify from 'dompurify';
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const clean = DOMPurify.sanitize('<b>hello</b><script>alert(1)</script>');
// → '<b>hello</b>'jsdom 要最小化
DOMPurify 本身不发任何网络请求。风险来自 jsdom:若开了 resources: 'usable' 之类配置,它会去加载外链资源(SSRF/隐私风险)。把 jsdom 当纯 DOM 宿主即可——不加载子资源、不执行外部脚本(这也是 jsdom 默认行为)。
同构项目(Next.js / Nuxt / Astro / SvelteKit) 推荐直接用封装:
import DOMPurify from 'isomorphic-dompurify';
// 浏览器走原生 DOMPurify,Node 端自动用 jsdom,API 完全一致
const clean = DOMPurify.sanitize(dirty);四、Trusted Types 集成
在启用 Trusted Types、且 CSP 要求 require-trusted-types-for 'script' 的页面,让 sanitize 直接产出可赋给受保护 sink 的值:
const clean = DOMPurify.sanitize(dirty, { RETURN_TRUSTED_TYPE: true });
// 支持的环境下返回 TrustedHTML,可直接赋给 innerHTML
el.innerHTML = clean;也可把 DOMPurify 接进一个 default policy:
window.trustedTypes.createPolicy('default', {
createHTML: (input) => DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
});递归陷阱
如果你创建的 policy 在 createHTML 内部调用 DOMPurify.sanitize,而 DOMPurify 内部又用了这个 policy,会无限递归。不要把一个会回调 sanitize 的 policy 再传回给 DOMPurify。
五、协议 / URI 控制
默认通过 ALLOWED_URI_REGEXP 限制链接协议,javascript: 等危险协议的 href/src 会被剥离:
DOMPurify.sanitize('<a href="javascript:alert(1)">x</a>');
// → '<a>x</a>' (危险 href 被移除,标签与文本保留)ALLOW_UNKNOWN_PROTOCOLS(默认false):设true会放行未知协议,扩大攻击面,谨慎使用;ALLOWED_URI_REGEXP:需要自定义允许协议时覆盖内置正则;ADD_URI_SAFE_ATTR:把某些属性额外标记为 URI 安全。
六、安全放行 iframe(按来源白名单)
需求:允许嵌入视频,但只信任 youtube。只 ADD_TAGS:['iframe'] 是不够的——必须校验 src 来源:
DOMPurify.addHook('uponSanitizeElement', (node, data) => {
if (data.tagName === 'iframe') {
const src = node.getAttribute('src') || '';
const ok = /^https:\/\/(www\.)?youtube\.com\//.test(src);
if (!ok) node.parentNode?.removeChild(node); // 非白名单来源直接移除
}
});
const clean = DOMPurify.sanitize(dirty, {
ADD_TAGS: ['iframe'],
ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'src'],
});放行标签 + 钩子校验来源,才能把信任限定在可信域名。
七、与 Markdown 管线协作
Markdown 渲染器(如 markdown-it)本身不是净化器。当允许内嵌 HTML 时,正确顺序是:
markdown 源 → 渲染成 HTML 字符串 → DOMPurify.sanitize(HTML) → 插入 DOMimport MarkdownIt from 'markdown-it';
import DOMPurify from 'dompurify';
const md = new MarkdownIt({ html: true });
const html = md.render(userMarkdown); // 先渲染
const clean = DOMPurify.sanitize(html); // 再净化「最终 HTML」
el.innerHTML = clean; // 最后插入注意净化对象是渲染后的 HTML,不是 Markdown 源文本;且仍要在插入前完成。
进入 指南 · 专家:返回类型与 TS 重载内幕、IN_PLACE 性能权衡、DOM Clobbering 与 SANITIZE_NAMED_PROPS、mXSS、与 CSP 的纵深防御。