HTML 清理器 API

社区工作组草案报告,

此版本:
https://wicg.github.io/sanitizer-api/
问题跟踪:
GitHub
编辑:
Frederik Braun (Mozilla)
Mario Heiderich (Cure53)
Daniel Vogelheim (Google LLC)
Tom Schuster (Mozilla)

摘要

本文档规定了一组 API,使开发者可以接收不受信任的 HTML 输入并将其净化,以便安全地插入到文档的 DOM 中。

本文档状态

本规范由 Web Platform Incubator Community Group 发布。 它不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,依据 W3C Community Contributor License Agreement (CLA), 存在有限的退出选项,且适用其他条件。 了解更多: W3C 社区与商业工作组

1. 引言

本节为非规范性内容。

Web 应用经常需要在客户端处理 HTML 字符串, 可能作为客户端模板解决方案的一部分,也可能作为渲染用户生成内容的一部分等。要以安全方式做到这一点很困难。 将字符串简单拼接后塞入 ElementinnerHTML 的朴素做法充满风险,因为它可能以多种出人意料的方式触发 JavaScript 执行。

诸如 [DOMPURIFY] 之类的库尝试 通过在插入前仔细解析并净化字符串、构建 DOM 并通过允许列表过滤其成员来管理这一问题。 但事实证明这是一种脆弱的方法,因为暴露给 Web 的解析 API 并不总能以合理方式映射到浏览器在“真实” DOM 中实际渲染字符串时的行为。 此外,这些库需要随时间跟进浏览器行为的变化;曾经安全的做法可能会因新的平台级特性而变成定时炸弹。

浏览器相当清楚自己何时会执行代码。我们可以通过教会浏览器如何以安全方式从任意字符串渲染 HTML 来改进用户空间库, 并以更有可能随着浏览器自身解析器实现变化而维护与更新的方式来实现。本文档概述了一个旨在完成此目标的 API。

1.1. 目标

1.2. API 概览

Sanitizer API 提供了将包含 HTML 的字符串解析为 DOM 树的功能,并按用户提供的配置过滤生成的树。方法以两种风格成对提供:

2. 框架

2.1. 清理器 API

Element 接口定义了两个方法,setHTML()setHTMLUnsafe()。 二者均接收包含 HTML 标记的 DOMString 与可选配置。

partial interface Element {
  [CEReactions] undefined setHTMLUnsafe((TrustedHTML or DOMString) html, optional SetHTMLUnsafeOptions options = {});
};
partial interface Element {
  [CEReactions] undefined setHTML(DOMString html, optional SetHTMLOptions options = {});
};
ElementsetHTMLUnsafe(html, options) 方法步骤为:
  1. compliantHTML 为调用 获取受信任类型兼容字符串 算法,传入 TrustedHTMLthis相关全局对象html、"Element setHTMLUnsafe" 和 "script" 后的结果。

  2. thistemplate 元素,则令 target 为其 template contents;否则为 this 本身。

  3. 设置并 过滤 HTML,给定 targetthiscompliantHTMLoptions 和 false。

ElementsetHTML(html, options) 方法步骤为:
  1. thistemplate, 则令 target 为其 template contents;否则为 this

  2. 设置并 过滤 HTML,给定 targetthishtmloptions 和 true。

partial interface ShadowRoot {
  [CEReactions] undefined setHTMLUnsafe((TrustedHTML or DOMString) html, optional SetHTMLUnsafeOptions options = {});
};
partial interface ShadowRoot {
  [CEReactions] undefined setHTML(DOMString html, optional SetHTMLOptions options = {});
};

这些方法也在 ShadowRoot 上提供:

ShadowRootsetHTMLUnsafe(html, options) 方法步骤为:
  1. compliantHTML 为调用 获取受信任类型兼容字符串 算法,并传入 TrustedHTMLthis相关全局对象html、"ShadowRoot setHTMLUnsafe" 和 "script" 后的结果。

  2. 设置并 过滤 HTML,使用 thisthisshadow host(作为上下文元素)、 compliantHTMLoptions 和 false。

ShadowRootsetHTML(html, options) 方法步骤为:
  1. 设置并 过滤 HTML,使用 this(作为目标)、this(作为上下文 元素)、 htmloptions 和 true。

Document 接口新增两个用于解析整个 Document 的方法:

partial interface Document {
  static Document parseHTMLUnsafe((TrustedHTML or DOMString) html, optional SetHTMLUnsafeOptions options = {});
};
partial interface Document {
  static Document parseHTML(DOMString html, optional SetHTMLOptions options = {});
};
parseHTMLUnsafe(html, options) 方法步骤为:
  1. compliantHTML 为调用 获取受信任类型兼容字符串 算法,并传入 TrustedHTML当前全局对象html、"Document parseHTMLUnsafe" 和 "script" 后的结果。

  2. document 为一个新的 Document, 其 content type 为 "text/html"。

    注意:由于 document 没有浏览上下文,脚本被禁用。

  3. documentallow declarative shadow roots 设为 true。

  4. 给定 documentcompliantHTML从字符串解析 HTML

  5. sanitizer 为使用 options 和 false 调用 根据选项获取一个 sanitizer 实例 的结果。

  6. document 上以 sanitizer 和 false 调用 sanitize

  7. 返回 document

parseHTML(html, options) 方法步骤为:
  1. document 为一个新的 Document, 其 content type 为 "text/html"。

    注意:由于 document 没有浏览上下文,脚本被禁用。

  2. documentallow declarative shadow roots 设为 true。

  3. 给定 documenthtml从字符串解析 HTML

  4. sanitizer 为使用 options 和 true 调用 根据选项获取一个 sanitizer 实例 的结果。

  5. document 上以 sanitizer 和 true 调用 sanitize

  6. 返回 document

2.2. SetHTML 选项与配置对象。

一系列类似 setHTML() 的 方法都接收一个选项字典。当前仅定义了该字典的一个成员:

enum SanitizerPresets { "default" };
dictionary SetHTMLOptions {
  (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = "default";
};
dictionary SetHTMLUnsafeOptions {
  (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = {};
};

Sanitizer 配置对象封装了过滤配置。同一份配置可用于 “安全” 与“不安全” 方法,其中“安全”方法会对传入配置隐式执行 removeUnsafe 操作,并在未传入配置时使用默认配置。默认配置在“安全”与“不安全”方法间不同:“安全”方法以默认安全为目标,使用更严格的默认;而“不安全”方法默认不受限制。配置的使用意图是:在页面生命周期早期构建一份(或少数几份)配置,此后按需复用。这允许实现对配置进行预处理。

配置对象可被查询以返回配置字典,也可直接修改。

[Exposed=Window]
interface Sanitizer {
  constructor(optional (SanitizerConfig or SanitizerPresets) configuration = "default");

  // Query configuration:
  SanitizerConfig get();

  // Modify a Sanitizer's lists and fields:
  boolean allowElement(SanitizerElementWithAttributes element);
  boolean removeElement(SanitizerElement element);
  boolean replaceElementWithChildren(SanitizerElement element);
  boolean allowProcessingInstruction(SanitizerPI pi);
  boolean removeProcessingInstruction(SanitizerPI pi);
  boolean allowAttribute(SanitizerAttribute attribute);
  boolean removeAttribute(SanitizerAttribute attribute);
  boolean setComments(boolean allow);
  boolean setDataAttributes(boolean allow);

  // Remove markup that executes script.
  boolean removeUnsafe();
};

Sanitizer 具有一个关联的 SanitizerConfig configuration

constructor(configuration) 方法步骤为:
  1. 如果 configurationSanitizerPresetsstring,则:

    1. 断言configuration default

    2. configuration 设为 内置安全默认配置

  2. valid 为在 this 上以 configuration 和 true 调用 设置配置 的返回值。

  3. valid 为 false,则抛出 TypeError

get() 方法步骤如下:
注: 除了 get() 方法外,Sanitizer 的元素和属性的顺序对外是不可见的。 通过对本方法的结果显式排序,允许实现内部优化, 比如在内部使用无序集合。
  1. configthisconfiguration

  2. 断言config有效的

  3. 如果 config["elements"] 存在

    1. 对于 config["elements"] 里的每个 element

      1. 如果 element["attributes"] 存在

        1. element["attributes"] 为对 element["attributes"] 调用 升序排序 的结果, 其中 attrA 小于项 attrB

      2. 如果 element["removeAttributes"] 存在

        1. element["removeAttributes"] 为对 element["removeAttributes"] 调用 升序排序 的结果, 其中 attrA 小于项 attrB

    2. config["elements"] 为对 config["elements"] 调用 升序排序 的结果, 其中 elementA 小于项 elementB

  4. 否则:

    1. config["removeElements"] 为对 config["removeElements"] 调用 升序排序 的结果, 其中 elementA 小于项 elementB

  5. 如果 config["replaceWithChildrenElements"] 存在

    1. config["replaceWithChildrenElements"] 为对 config["replaceWithChildrenElements"] 调用 升序排序 的结果, 其中 elementA 小于项 elementB

  6. 如果 config["processingInstructions"] 存在

    1. config["processingInstructions"] 为对 config["processingInstructions"] 调用 升序排序 的结果, 其中 piA["target"] 码元小于 piB["target"]。

  7. 否则:

    1. config["removeProcessingInstructions"] 为对 config["removeProcessingInstructions"] 调用 升序排序 的结果, 其中 piA["target"] 码元小于 piB["target"]。

  8. 如果 config["attributes"] 存在

    1. config["attributes"] 为对 config["attributes"] 调用 升序排序 的结果, 其中 attrA 小于项 attrB

  9. 否则:

    1. config["removeAttributes"] 为对 config["removeAttributes"] 调用 升序排序 的结果, 其中 attrA 小于项 attrB

  10. 返回 config

allowElement(element) 方法步骤如下:
注: 此算法较为复杂,因为元素允许列表可能会为不同元素分别指定 允许列表或移除列表。这就要求我们区分以下四种情况:
  • 是否存在全局允许列表或移除列表,以及

  • 这些列表中是否已经包含 element

  1. configurationthisconfiguration

  2. 断言configuration有效的

  3. element 为 用 规范化带属性的 sanitizer 元素 处理 element 的结果。

  4. 如果 configuration["elements"] 存在

    1. modified 为对 configuration["replaceWithChildrenElements"] 调用 remove element 的结果。

    2. 注释:我们需要确保单元素属性不会与全局属性重叠。

    3. 如果 configuration["attributes"] 存在

      1. 如果 element["attributes"] 存在

        1. element["attributes"] 为对 element["attributes"] 调用 去重 的结果。

        2. element["attributes"] 为 差集 的结果, 即 element["attributes"] 和 configuration["attributes"]。

        3. 如果 configuration["dataAttributes"] 为 true:

          1. 移除所有 item 项 从 element["attributes"], 其中 item自定义 data-属性

      2. 如果 element["removeAttributes"] 存在

        1. element["removeAttributes"] 为对 element["removeAttributes"] 调用 去重 结果。

        2. element["removeAttributes"] 为 交集 的结果, 即 element["removeAttributes"] 和 configuration["attributes"]。

    4. 否则:

      1. 如果 element["attributes"] 存在

        1. element["attributes"] 为对 element["attributes"] 调用 去重 结果。

        2. element["attributes"] 为 差集 的结果, 即 element["attributes"] 和 element["removeAttributes"],默认为 « »。

        3. 移除 element["removeAttributes"]。

        4. element["attributes"] 为 差集 的结果, 即 element["attributes"] 和 configuration["removeAttributes"]。

      2. 如果 element["removeAttributes"] 存在

        1. element["removeAttributes"] 为对 element["removeAttributes"] 调用 去重 结果。

        2. element["removeAttributes"] 为 差集 的结果, 即 element["removeAttributes"] 和 configuration["removeAttributes"]。

    5. 如果 configuration["elements"] 不包含 element

      1. 注释:这是全局允许列表但尚未包含 element 的情况。

      2. 追加 elementconfiguration["elements"]。

      3. 返回 true。

    6. 注释:这是全局允许列表且已包含 element 的情况。

    7. current elementconfiguration["elements"] 中 item, 其中 item["name"] 等于 element["name"] 且 item["namespace"] 等于 element["namespace"]。

    8. 如果 element 等于 current element 则返回 modified

    9. 移除 elementconfiguration["elements"]。

    10. 追加 elementconfiguration["elements"]

    11. 返回 true。

  5. 否则:

    1. 如果 element["attributes"] 存在element["removeAttributes"] 默认为 « » 非

      1. 用户代理可以 向控制台报告警告,说明该操作不被支持。

      2. 返回 false。

    2. modified 为对 configuration["replaceWithChildrenElements"] 调用 remove element 的结果。

    3. 如果 configuration["removeElements"] 不包含 element

      1. 注释:这是全局移除列表未包含 element 的情况。

      2. 返回 modified

    4. 注释:这是全局移除列表包含 element 的情况。

    5. 移除 elementconfiguration["removeElements"]。

    6. 返回 true。

removeElement(element) 方法步骤为 使用 elementthisconfiguration移除一个元素
replaceElementWithChildren(element) 方法的步骤为:
  1. configuration此对象(this)配置

  2. 断言configuration有效 的。

  3. element 设为以 element 作为参数调用 规范化 sanitizer 元素 的结果。

  4. 如果 内置不可替换元素列表 包含 element

    1. 返回 false。

  5. 如果 configuration["replaceWithChildrenElements"] 包含 element:

    1. 返回 false。

  6. configuration["removeElements"] 中移除 element

  7. configuration["elements"] 列表中移除 element

  8. 添加 elementconfiguration["replaceWithChildrenElements"]。

  9. 返回 true。

allowProcessingInstruction(pi) 方法步骤如下:
  1. configurationthisconfiguration

  2. 断言configuration有效的

  3. pi 为用 规范化处理指令处理 pi 的结果。

  4. 如果 configuration["processingInstructions"] 存在

    1. 如果 configuration["processingInstructions"] 包含 pi

      1. 返回 false。

    2. 追加 piconfiguration["processingInstructions"]。

    3. 返回 true。

  5. 否则:

    1. 如果 configuration["removeProcessingInstructions"] 包含 pi

      1. 移除 configuration["removeProcessingInstructions"] 中 "target" 等于 pi["target"] 的项。

      2. 返回 true。

    2. 返回 false。

removeProcessingInstruction(pi) 方法步骤如下:
  1. configurationthisconfiguration

  2. 断言configuration有效的

  3. pi 为用 规范化处理指令处理 pi 的结果。

  4. 如果 configuration["processingInstructions"] 存在

    1. 如果 configuration["processingInstructions"] 包含 pi

      1. 移除 configuration["processingInstructions"] 中 "target" 等于 pi["target"] 的项。

      2. 返回 true。

    2. 返回 false。

  5. 否则:

    1. 如果 configuration["removeProcessingInstructions"] 包含 pi

      1. 返回 false。

    2. 追加 piconfiguration["removeProcessingInstructions"]。

    3. 返回 true。

allowAttribute(attribute) 方法步骤如下:
注: 此方法区分两种情况,即全局允许列表和全局移除列表。如果要将 attribute 添加到全局允许列表, 也许还需修正每个元素的允许/移除列表以保持有效性要求。
  1. configurationthisconfiguration

  2. 断言configuration有效的

  3. attribute 为用 规范化属性处理 attribute 的结果。

  4. 如果 configuration["attributes"] 存在

    1. 注释:如果有全局允许列表,需要添加 attribute

    2. 如果 configuration["dataAttributes"] 为 true,且 attribute自定义 data 属性,则返回 false。

    3. 如果 configuration["attributes"] 包含 attribute 返回 false。

    4. 注释:修正每个元素的允许/移除列表。

    5. 如果 configuration["elements"] 存在

      1. 对于 configuration["elements"] 中的每个 element

        1. 如果 element["attributes"] 默认为 « » 包含 attribute

          1. 移除 element["attributes"] 中的 attribute

        2. 断言element["removeAttributes"] 默认为 « » 不 包含 attribute

    6. 追加 attributeconfiguration["attributes"]

    7. 返回 true。

  5. 否则:

    1. 注释:如果有全局移除列表,需要移除 attribute

    2. 如果 configuration["removeAttributes"] 不 包含 attribute

      1. 返回 false。

    3. 移除 attributeconfiguration["removeAttributes"]。

    4. 返回 true。

removeAttribute(attribute) 方法步骤为 用 移除属性,参数为 attribute 以及 thisconfiguration
setComments(allow) 方法步骤如下:
  1. configurationthisconfiguration

  2. 断言configuration有效的

  3. 如果 configuration["comments"] 存在configuration["comments"] 等于 allow,则返回 false;

  4. configuration["comments"] 设为 allow

  5. 返回 true。

setDataAttributes(allow) 方法步骤如下:
  1. configurationthisconfiguration

  2. 断言configuration有效的

  3. 如果 configuration["attributes"] 不 存在,则返回 false。

  4. 如果 configuration["dataAttributes"] 等于 allow,则返回 false。

  5. 如果 allow 为 true:

    1. 移除 configuration["attributes"] 中所有 attr,其中 attr自定义 data 属性

    2. 如果 configuration["elements"] 存在

      1. 对于 configuration["elements"] 中的每个 element

        1. 如果 element["attributes"] 存在

          1. 移除 element["attributes"] 中所有 attr,其中 attr自定义 data 属性

  6. configuration["dataAttributes"] 设为 allow

  7. 返回 true。

removeUnsafe() 方法步骤为 使用 thisconfiguration, 调用 remove unsafe, 并用返回结果来更新 thisconfiguration

2.3. 配置字典

dictionary SanitizerElementNamespace {
  required DOMString name;
  DOMString? _namespace = "http://www.w3.org/1999/xhtml";
};

// Used by "elements"
dictionary SanitizerElementNamespaceWithAttributes : SanitizerElementNamespace {
  sequence<SanitizerAttribute> attributes;
  sequence<SanitizerAttribute> removeAttributes;
};

typedef (DOMString or SanitizerElementNamespace) SanitizerElement;
typedef (DOMString or SanitizerElementNamespaceWithAttributes) SanitizerElementWithAttributes;

dictionary SanitizerProcessingInstruction {
  required DOMString target;
};

typedef (DOMString or SanitizerProcessingInstruction) SanitizerPI;

dictionary SanitizerAttributeNamespace {
  required DOMString name;
  DOMString? _namespace = null;
};
typedef (DOMString or SanitizerAttributeNamespace) SanitizerAttribute;

dictionary SanitizerConfig {
  sequence<SanitizerElementWithAttributes> elements;
  sequence<SanitizerElement> removeElements;
  sequence<SanitizerElement> replaceWithChildrenElements;

  sequence<SanitizerPI> processingInstructions;
  sequence<SanitizerPI> removeProcessingInstructions;

  sequence<SanitizerAttribute> attributes;
  sequence<SanitizerAttribute> removeAttributes;

  boolean comments;
  boolean dataAttributes;
};

2.4. 配置不变式

配置可以且应该由开发者根据其用途进行修改。可选方式包括从零开始编写新的配置字典,通过修改方法修改已有Sanitizer的配置,或用get()得到已有Sanitizer配置字典,修改后再创建新Sanitizer实例。

空配置(当用“unsafe”方法如setHTMLUnsafe调用时)允许所有内容。 配置"default"包含内置安全默认配置。 注意“safe”和“unsafe”净化方法有不同的默认值。

并非所有配置字典都是有效的。有效配置应避免冗余(如重复允许同一元素)和矛盾(如同时移除和允许同一元素)。

配置要有效,需满足如下条件:

elements 元素允许列表还可以为给定元素指定允许或移除的属性。这意在映射到 [HTML] 的结构,其同时包含 全局属性 和适用于特定元素的局部 属性。全局与局部属性可以混用,但请注意,如果某个配置存在歧义——某属性在一处被允许而在另一处被禁止——通常视为无效。

全局 attributes 全局 removeAttributes
局部 attributes 若属性在任一列表中匹配,则允许该属性。不得有重复条目。 只有当属性在局部允许列表中时才允许。 全局移除与局部允许列表之间不得有重复条目。 注意:全局移除列表对这个特定元素不起作用,但可能适用于没有局部允许列表的其他元素。
局部 removeAttributes 若属性在全局允许列表中且不在局部移除列表中,则允许该属性。局部移除必须是全局允许列表的子集。 若属性既不在全局移除列表也不在局部移除列表中,则允许该属性。 全局移除与局部移除列表之间不得有重复条目。

请注意其中的不对称性:大多数情况下,全局与逐元素列表之间不允许有重复;但当存在全局允许列表与逐元素移除列表时,后者必须是前者的子集。上表中仅聚焦重复条目的摘录如下:

全局 attributes 全局 removeAttributes
局部 attributes 不允许有重复条目。 不允许有重复条目。
局部 removeAttributes 局部移除必须是全局允许列表的子集。 不允许有重复条目。

dataAttributes 设置允许 自定义数据属性。若将 dataAttributes 视为一个允许列表,则上述规则可直接延伸到 自定义数据属性

全局 attributesdataAttributes 已设置
局部 attributes 所有 自定义数据属性 都被允许。任何 自定义数据属性 都不得被列入任何允许列表,否则将造成重复条目。
局部 removeAttributes 除非被列入局部移除列表,否则 自定义数据属性 是被允许的。 任何 自定义数据属性 都不得被列入全局允许列表,否则将造成重复条目。

用文字表述这些规则:

要判定一个规范化 SanitizerConfig config 是否 有效

注意:期望传入的配置已事先经过 规范化配置步骤。 这里只会断言该算法应已保证成立的条件。

  1. 断言config["elements"] 存在config["removeElements"] 存在

  2. 如果 config["elements"] 存在,且 config["removeElements"] 存在,则返回 false。

  3. 断言:要么 config["processingInstructions"] 存在, 要么 config["removeProcessingInstructions"] 存在

  4. 如果 config["processingInstructions"] 存在,且 config["removeProcessingInstructions"] 存在,则返回 false。

  5. 断言:要么 config["attributes"] 存在, 要么 config["removeAttributes"] 存在

  6. 如果 config["attributes"] 存在,且 config["removeAttributes"] 存在,则返回 false。

  7. 断言config 里的所有 SanitizerElementNamespaceWithAttributesSanitizerElementNamespaceSanitizerProcessingInstruction、 以及 SanitizerAttributeNamespace 条目都是规范化的,即它们已经 分别通过 规范化元素规范化处理指令、或 规范化属性 处理。

  8. 如果 config["elements"] 存在

    1. 如果 config["elements"] 有重复项,则返回 false。

  9. 否则:

    1. 如果 config["removeElements"] 有重复项,则返回 false。

  10. 如果 config["replaceWithChildrenElements"] 存在有重复项,则返回 false。

  11. 如果 config["processingInstructions"] 存在

    1. 如果 config["processingInstructions"] 有重复目标,则返回 false。

  12. 否则:

    1. 如果 config["removeProcessingInstructions"] 有重复目标,则返回 false。

  13. 如果 config["attributes"] 存在

    1. 如果 config["attributes"] 有重复项,则返回 false。

  14. 否则:

    1. 如果 config["removeAttributes"] 有重复项,则返回 false。

  15. 如果 config["replaceWithChildrenElements"] 存在

    1. 对于 config["replaceWithChildrenElements"] 里的每个 element

      1. 如果 内建不可替换元素列表 包含 element, 则返回 false。

    2. 如果 config["elements"] 存在

      1. 如果 交集 config["elements"] 和 config["replaceWithChildrenElements"] 非 , 则返回 false。

    3. 否则:

      1. 如果 交集 config["removeElements"] 和 config["replaceWithChildrenElements"] 非 , 则返回 false。

  16. 如果 config["attributes"] 存在

    1. 断言config["dataAttributes"] 存在

    2. 如果 config["elements"] 存在

      1. 对于 config["elements"] 里的每个 element

        1. 如果 element["attributes"] 存在element["attributes"] 有重复项, 则返回 false。

        2. 如果 element["removeAttributes"] 存在,且 element["removeAttributes"] 有重复项, 则返回 false。

        3. 如果 交集 config["attributes"] 和 element["attributes"] 默认为 « » 非 ,则返回 false。

        4. 如果 element["removeAttributes"] 默认为 « » 不是 config["attributes"] 的子集, 则返回 false。

        5. 如果 config["dataAttributes"] 为 true,且 element["attributes"] 含有 自定义 data 属性,则返回 false。

    3. 如果 config["dataAttributes"] 为 true 且 config["attributes"] 含有 自定义 data 属性,则返回 false。

  17. 否则:

    1. 如果 config["elements"] 存在

      1. 对于 config["elements"] 里的每个 element

        1. 如果 element["attributes"] 存在,且 element["removeAttributes"] 存在, 则返回 false。

        2. 如果 element["attributes"] 存在, 且 element["attributes"] 有重复项, 则返回 false。

        3. 如果 element["removeAttributes"] 存在, 且 element["removeAttributes"] 有重复项, 则返回 false。

        4. 如果 交集 config["removeAttributes"] 和 element["attributes"] 默认为 « » 非 ,则返回 false。

        5. 如果 交集 config["removeAttributes"] 和 element["removeAttributes"] 默认为 « » 非 ,则返回 false。

    2. 如果 config["dataAttributes"] 存在,则返回 false。

  18. 返回 true。

注意:字典设置 配置时,会做一些归一化处理。特别是,如果允许/移除列表都缺失,会将其当作空的移除列表。 所以 {} 本身不是 有效 配置, 但会被归一化为 {removeElements:[],removeAttributes:[]},这是有效的。 选用这种规范化,是为了让缺失字典和空字典一致, 即让 setHTMLUnsafe(txt)setHTMLUnsafe(txt, {sanitizer: {}}) 保持一致。

3. 算法

若要设置并过滤 HTML,给定一个 ElementDocumentFragment target,一个 Element contextElement,一个 字符串 html,一个 字典 options 和一个 布尔值 safe
  1. 如果 safe 为真且 contextElement本地名 为 "script",且 contextElement命名空间HTML 命名空间SVG 命名空间,则返回。

  2. sanitizer 为调用 根据 options 获取 sanitizer 实例 的结果,参数为 optionssafe

  3. newChildren 为根据 HTML 片段解析算法,传入 contextElementhtml 和 true 的结果。

  4. fragment 为一个新的 DocumentFragment, 其 节点文档contextElement节点文档

  5. 遍历 newChildren 中的每一个 node追加 nodefragment

  6. fragment 使用 sanitizersafe 执行 sanitize

  7. targetfragment 替换所有

要根据 字典 options 和一个 布尔值 safe获取 sanitizer 实例

注意: 该算法适用于 SetHTMLOptionsSetHTMLUnsafeOptions, 它们仅在默认值上有所不同。

  1. sanitizerSpec 为 "default"。

  2. 如果 options["sanitizer"] 存在,则:

    1. sanitizerSpec 设为 options["sanitizer"]

  3. 断言sanitizerSpec 必须为 Sanitizer 实例、 一个 字符串,该字符串为 SanitizerPresets 枚举成员,或者是一个 字典

  4. 如果 sanitizerSpec字符串

    1. 断言sanitizerSpec 等于 "default"

    2. sanitizerSpec 设为 内建安全默认配置

  5. 断言sanitizerSpecSanitizer 实例,或者 字典

  6. 如果 sanitizerSpec字典

    1. sanitizer 为一个新的 Sanitizer 实例。

    2. setConfigurationResult 为在 sanitizer 上用 sanitizerSpec 以及 not safe 设置配置 的结果。

    3. 如果 setConfigurationResult 为 false,抛出 TypeError

    4. sanitizerSpec 设为 sanitizer

  7. 断言sanitizerSpecSanitizer 实例。

  8. 返回 sanitizerSpec

3.1. 净化

对于主要的 sanitize 操作,使用一个 ParentNode node,一个 Sanitizer sanitizer,以及一个 布尔值 safe,执行以下步骤:
  1. configurationsanitizerconfiguration 的值。

  2. 断言configuration有效

  3. 如果 safe 为 true,则将 configuration 设为对其调用 remove unsafe 后的结果。

  4. nodeconfiguration 以及 handleJavascriptNavigationUrls 设为 safe,调用 sanitize core

sanitize core 操作,使用 ParentNode nodeSanitizerConfig configuration,以及 布尔值 handleJavascriptNavigationUrls,递归遍历以 node 为起点的 DOM 树。具体步骤如下:
  1. 对于 nodechildren 里的每个 child

    1. 断言child 实现 TextCommentElementProcessingInstructionDocumentType

      注意:目前该算法仅对 HTML 解析器输出调用,上述断言应满足。DocumentType 仅会出现在 parseHTMLparseHTMLUnsafe。 如果将来该算法用于其它场景,则需重新检查此假设。

    2. 如果 child 实现 DocumentType继续

    3. 如果 child 实现 Text继续

    4. 如果 child 实现 Comment

      1. 如果 configuration["comments"] 不为 true, 移除 child

    5. 如果 child 实现 ProcessingInstruction

      1. piTargetchildtarget

      2. 如果 configuration["processingInstructions"] 存在

        1. 如果 configuration["processingInstructions"] 不 包含 piTarget

          1. 移除 child

      3. 否则:

        1. 如果 configuration["removeProcessingInstructions"] 包含 piTarget

          1. 移除 child

    6. 否则:

      1. elementNameSanitizerElementNamespace ,值为 child本地名命名空间

      2. 如果 configuration["replaceWithChildrenElements"] 存在, 且 configuration["replaceWithChildrenElements"] 包含 elementName

        1. 断言node实现 Document

        2. 对子 child 以及 configurationhandleJavascriptNavigationUrls 调用 sanitize core

        3. 调用 replace all, 用 childchildren 替换 child

        4. 继续

      3. 如果 configuration["elements"] 存在

        1. 如果 configuration["elements"] 不 包含 elementName

          1. 移除 child

          2. 继续

      4. 否则:

        1. 如果 configuration["removeElements"] 包含 elementName

          1. 移除 child

          2. 继续

      5. 如果 elementName 等于 «[ "name" → "template", "namespace" → HTML namespace ]», 则对子 childtemplate contents 调用 sanitize core,参数为 configurationhandleJavascriptNavigationUrls

      6. 如果 childshadow host, 对 childshadow root 调用 sanitize core,参数为 configurationhandleJavascriptNavigationUrls

      7. elementWithLocalAttributes 为 « [] »。

      8. 如果 configuration["elements"] 存在configuration["elements"] 包含 elementName

        1. elementWithLocalAttributes 设为 configuration["elements"][elementName]。

      9. 对于 childattribute list 里的每个 attribute

        1. attrNameSanitizerAttributeNamespace ,值为 attribute本地名命名空间

        2. 如果 elementWithLocalAttributes["removeAttributes"] 默认为 « » 包含 attrName

          1. 移除 attribute

        3. 否则,如果 configuration["attributes"] 存在

          1. 如果 configuration["attributes"] 不 包含 attrName, 且 elementWithLocalAttributes["attributes"] 默认为 « » 不 包含 attrName,并且 "data-" 不是 码元前缀attribute本地名 ,且 命名空间 不是 null ,或者 configuration["dataAttributes"] 不为 true:

            1. 移除 attribute

        4. 否则:

          1. 如果 elementWithLocalAttributes["attributes"] 存在elementWithLocalAttributes["attributes"] 不 包含 attrName

            1. 移除 attribute

          2. 否则,如果 configuration["removeAttributes"] 包含 attrName

            1. 移除 attribute

        5. 如果 handleJavascriptNavigationUrls

          1. 如果 «[elementName, attrName]» 匹配 内建导航 URL 属性列表 的一项,且 attribute 包含 javascript: URL,则 移除 attribute

          2. 如果 child命名空间 等于 MathML 命名空间,且 attr本地名 等于 "href" 且 attr命名空间null 或为 XLink 命名空间attr 包含 javascript: URL, 则 移除 attribute

          3. 如果 内建动画 URL 属性列表 包含 «[elementName, attrName]»,且 attr 等于 "href" 或 "xlink:href",则 移除 attribute

      10. 对子 childconfigurationhandleJavascriptNavigationUrls 调用 sanitize core

注意:当前浏览器只在导航时支持 javascript: URL。 由于导航本身不是 XSS 威胁,我们会处理 javascript: URL 的导航,但不会阻断所有导航。

声明式导航大致有几类:

  1. 锚点元素。(HTML 和 SVG 命名空间下的 <a> 元素)

  2. 表单元素,在表单 action 处触发导航。

  3. [MathML] 允许 任意元素可作为锚点

  4. [SVG11] 动画。

前两类在 内建导航 URL 属性列表覆盖。

MathML 情况用单独规则覆盖,因为本规范没有“每命名空间全局”规则的形式化表示。

SVG 动画情形用 内建动画 URL 属性列表覆盖。但 因为 SVG 动画元素解释依赖动画目标,而在净化时无法得知最终目标, sanitize 算法会阻断所有关于 href 属性的动画。

要判定 attribute 是否 包含 javascript: URL
  1. url 为对 attribute运行基本 URL 解析器的结果。

  2. 如果 urlfailure,则返回 false。

  3. 返回 urlscheme 是否 等于 "javascript"。

3.2. 修改配置

配置修改方法是 Sanitizer 上用于修改其配置的方法。 它们会保持配置的有效性要求。 方法返回一个布尔值,用于告知调用者配置是否发生了更改。

let s = new Sanitizer({elements: ["div"]});
s.allowElement("p"); // 返回 true。
div.setHTML("<div><p>", {sanitizer: s});  // 允许 `<div>` 和 `<p>`。
let s = new Sanitizer({elements: ["div"]});
s.removeElement("p");  // 返回 false,因为 <p> 之前未被允许。
div.setHTML("<div><p>", {sanitizer: s});  // 只允许 `<div>`。`<p>` 会被移除。
要从 SanitizerConfig configuration移除元素 SanitizerElement element
注意: 本方法需要区分四种情况:
  • 是使用全局允许列表还是全局移除列表,

  • 对应列表是否已经包含 element

  1. 断言configuration有效的。

  2. element 为用 规范化元素处理 element 的结果。

  3. modifiedremove elementconfiguration["replaceWithChildrenElements"] 的结果。

  4. 如果 configuration["elements"] 存在

    1. 如果 configuration["elements"] 包含 element

      1. 注释:我们有全局允许列表且已包含 element

      2. 移除 elementconfiguration["elements"]。

      3. 返回 true。

    2. 注释:我们有全局允许列表,但不包含 element

    3. 返回 modified

  5. 否则:

    1. 如果 configuration["removeElements"] 包含 element

      1. 注释:我们有全局移除列表,且其中已含 element

      2. 返回 modified

    2. 注释:我们有全局移除列表,但其中不含 element

    3. 添加 elementconfiguration["removeElements"]。

    4. 返回 true。

要从 SanitizerConfig configuration移除属性 SanitizerAttribute attribute

注意:此方法区分两种情形,即全局允许列表还是全局移除列表。如果向全局移除列表添加 attribute, 可能需要额外处理每个元素的允许或移除列表,以保证有效性要求。如果从全局允许列表移除 attribute,也可能要从本地移除列表一并删去该属性。

  1. 断言configuration有效的。

  2. attribute 为用 规范化属性处理 attribute 的结果。

  3. 如果 configuration["attributes"] 存在

    1. 注释:如果有全局允许列表,需要移除 attribute

    2. modifiedremove attributeconfiguration["attributes"] 的结果。

    3. 注释:修正各元素特定的允许/移除列表。

    4. 如果 configuration["elements"] 存在

      1. 对于 configuration["elements"] 里的每个 element

        1. 如果 element["attributes"] 默认为 « » 包含 attribute

          1. modified = true。

          2. 移除 attributeelement["attributes"]。

        2. 如果 element["removeAttributes"] 默认为 « » 包含 attribute

          1. 断言modified 为 true。

          2. 移除 attributeelement["removeAttributes"]。

    5. 返回 modified

  4. 否则:

    1. 注释:如果有全局移除列表,需要添加 attribute

    2. 如果 configuration["removeAttributes"] 包含 attribute 返回 false。

    3. 注释:修正各元素特定的允许/移除列表。

    4. 如果 configuration["elements"] 存在

      1. 对于 configuration["elements"] 中的每个 element

        1. 如果 element["attributes"] 默认为 « » 包含 attribute

          1. 移除 attributeelement["attributes"]。

        2. 如果 element["removeAttributes"] 默认为 « » 包含 attribute

          1. 移除 attributeelement["removeAttributes"]。

    5. 添加 attributeconfiguration["removeAttributes"]。

    6. 返回 true。

要从 SanitizerConfig configuration移除不安全内容,应执行:

注意:虽然本算法被命名为 remove unsafe, 实际上我们仅在本规范意义下 用“unsafe”指代 插入文档后会执行 JavaScript 的内容。换言之, 此方法会消除 XSS 攻击的机会。

  1. 断言内置安全基线配置键集合 等于 « [ "removeElements", "removeAttributes" ] »。

  2. 断言configuration有效的。

  3. result 为 false。

  4. 对于 内建安全基线配置["removeElements"] 里的每个 element

    1. 调用 移除元素,参数为 elementconfiguration

    2. 如果调用结果为 true,则将 result 设为 true。

  5. 对于 内建安全基线配置["removeAttributes"] 里的每个 attribute

    1. 调用 移除属性,参数为 attributeconfiguration

    2. 如果调用结果为 true,则将 result 设为 true。

  6. 对于 事件处理器内容属性 里的每个 attribute

    1. 调用 移除属性,参数为 attributeconfiguration

    2. 如果调用结果为 true,则将 result 设为 true。

  7. 返回 result

3.3. 设置配置

设置配置,给定一个 字典 configuration, 一个 布尔值 allowCommentsPIsAndDataAttributes,以及一个 Sanitizer sanitizer
  1. configuration 使用 allowCommentsPIsAndDataAttributes 执行 规范化

  2. 如果 configuration 不是 有效,则返回 false。

  3. sanitizerconfiguration 设置为 configuration

  4. 返回 true。

3.4. 规范化配置

Sanitizer 以规范化形式存储配置,这样会让多种处理步骤变得更简单。

一个 elements 列表 {elements: ["div"]} 存储时会变成 {elements: [{name: "div", namespace: "http://www.w3.org/1999/xhtml"}]
规范化配置,输入 SanitizerConfig configuration 和一个布尔值 allowCommentsPIsAndDataAttributes

注意:假定 configuration 已由 [WebIDL] 将 JavaScript 值转换为 SanitizerConfig 的结果。

  1. 如果 configuration["elements"] 和 configuration["removeElements"] 均不存在,则设定 configuration["removeElements"] 为 « »。

  2. 如果 configuration["processingInstructions"] 和 configuration["removeProcessingInstructions"] 均不存在

    1. 如果 allowCommentsPIsAndDataAttributes 为 true,设定 configuration["removeProcessingInstructions"] 为 « »。

    2. 否则,设定 configuration["processingInstructions"] 为 « »。

  3. 如果 configuration["attributes"] 和 configuration["removeAttributes"] 均不存在,则设定 configuration["removeAttributes"] 为 « »。

  4. 如果 configuration["elements"] 存在

    1. elements = « »。

    2. 对于 configuration["elements"] 里的每个 element

      1. 规范化带属性的净化元素 element 的结果添加到 elements

    3. 设置 configuration["elements"] 为 elements

  5. 如果 configuration["removeElements"] 存在

    1. elements = « »。

    2. 对于 configuration["removeElements"] 里的每个 element

      1. 规范化元素 element 的结果添加elements

    3. 设置 configuration["removeElements"] 为 elements

  6. 如果 configuration["replaceWithChildrenElements"] 存在

    1. elements = « »。

    2. 对于 configuration["replaceWithChildrenElements"] 里的每个 element

      1. 规范化元素 element 的结果添加elements

    3. 设置 configuration["replaceWithChildrenElements"] 为 elements

  7. 如果 configuration["processingInstructions"] 存在

    1. processingInstructions = « »。

    2. 对于 configuration["processingInstructions"] 中的每个 pi

      1. 规范化处理指令 pi 的结果添加processingInstructions

    3. 设置 configuration["processingInstructions"] 为 processingInstructions

  8. 如果 configuration["removeProcessingInstructions"] 存在

    1. processingInstructions = « »。

    2. 对于 configuration["removeProcessingInstructions"] 的每个 pi

      1. 规范化处理指令 pi 的结果添加processingInstructions

    3. 设置 configuration["removeProcessingInstructions"] 为 processingInstructions

  9. 如果 configuration["attributes"] 存在

    1. attributes = « »。

    2. 对于 configuration["attributes"] 的每个 attribute

      1. 规范化属性 attribute 的结果添加attributes

    3. 设置 configuration["attributes"] 为 attributes

  10. 如果 configuration["removeAttributes"] 存在

    1. attributes = « »。

    2. 对于 configuration["removeAttributes"] 的每个 attribute

      1. 规范化属性 attribute 的结果添加attributes

    3. 设置 configuration["removeAttributes"] 为 attributes

  11. 如果 configuration["comments"] 不存在,则设定 configuration["comments"] 为 allowCommentsPIsAndDataAttributes

  12. 如果 configuration["attributes"] 存在configuration["dataAttributes"] 不存在,则 设置 configuration["dataAttributes"] 为 allowCommentsPIsAndDataAttributes

规范化带属性的元素 SanitizerElementWithAttributes element
  1. result 为用 规范化元素处理 element 的结果。

  2. 如果 element字典

    1. 如果 element["attributes"] 存在

      1. attributes = « »。

      2. 对于 element["attributes"] 的每个 attribute

        1. 规范化属性 attribute 的结果添加attributes

      3. 设置 result["attributes"] 为 attributes

    2. 如果 element["removeAttributes"] 存在

      1. attributes = « »。

      2. 对于 element["removeAttributes"] 的每个 attribute

        1. 规范化属性 attribute 的结果添加attributes

      3. 设置 result["removeAttributes"] 为 attributes

  3. 如果 result["attributes"] 和 result["removeAttributes"] 均不存在

    1. 设置 result["removeAttributes"] 为 « »。

  4. 返回 result

规范化元素 SanitizerElement element, 返回用 element 以及 HTML 命名空间 作为默认命名空间,调用规范化名称的结果。
规范化处理指令 pi,按如下步骤:
  1. 断言piDOMString字典

  2. 如果 piDOMString, 则返回 «[ "target" → pi ]»。

  3. 断言pi字典pi["target"] 存在

  4. 返回 «[ "target" → pi["target"] ]»。

规范化属性 SanitizerAttribute attribute, 返回用 attribute 和 null 作为默认命名空间,调用 规范化名称 的结果。
规范化名称 name,默认命名空间为 defaultNamespace,执行以下步骤:
  1. 断言name 必须是 DOMString字典

  2. 如果 nameDOMString, 返回 «[ "name" → name, "namespace" → defaultNamespace]»。

  3. 断言name字典,且 name["name"] 和 name["namespace"] 均存在

  4. 如果 name["namespace"] 为空字符串,则将其设为 null。

  5. 返回 «[
    "name" → name["name"],
    "namespace" → name["namespace"]
    ]»。

3.5. 辅助算法

对于本规范中使用的 规范化 元素属性名 列表,成员资格基于同时匹配 "name" 和 "namespace" 两个条目:

如果 Sanitizer 名称列表 list 存在某个 entry(为有序映射),且 item["name"] 等于 entry["name"] 且 item["namespace"] 等于 entry["namespace"],则 contains(包含) item
如果 Sanitizer 目标列表 list 存在某个 entry(为有序映射),且 target 等于 entry["target"],则 contains a target(包含目标) target
要从列表 list 移除 item
  1. 设置 removed 为 false。

  2. 对于 list 中的每个 entry

    1. 如果 item["name"] 等于 entry["name"] 且 item["namespace"] 等于 entry["namespace"]:

      1. list 中移除 entry

      2. 设置 removed 为 true。

  3. 返回 removed

list 添加 name已规范化,且 list有序映射) 的 add 算法:
  1. 如果 list 包含 name,则直接返回。

  2. 追加 namelist

判断 itemA 是否小于项 itemB
  1. 如果 itemA["namespace"] 为 null:

    1. 如果 itemB["namespace"] 不为 null,则返回 true。

  2. 否则:

    1. 如果 itemB["namespace"] 为 null,则返回 false。

    2. 如果 itemA["namespace"] 码元小于 itemB["namespace"],则返回 true。

    3. 如果 itemA["namespace"] 不等于 itemB["namespace"],则返回 false。

  3. 返回 itemA["name"] 是否 码元小于 itemB["name"]。

有序集合的相等性为成员相等但不考虑顺序: 有序集合 AB 相等,当且仅当 AB 的超集,且 B 也是 A 的超集
有序映射是一组元组序列有序映射的相等性是这些元组作为 有序集合时的相等性。 有序映射 AB 相等,当且仅当 AentriesBentries 组成的有序集合 相等
列表 list 有重复项,如果对于任意 itemlist 中存在多个 entry 满足 item["name"] 等于 entry["name"] 且 item["namespace"] 等于 entry["namespace"]。
列表 list 有重复目标,如果对于任意 itemlist 中存在多个 entry 满足 item["target"] 等于 entry["target"]。
要从列表 list去重
  1. result = « »。

  2. 对于 list 中的每个 entry添加 entryresult

  3. 返回 result

两个包含 SanitizerElement列表 AB交集集合交集 相同, 但集合中的条目需先经过 规范化
  1. set A 为 « [] »。

  2. set B 为 « [] »。

  3. 对于 A 中的每个 entry,将 规范化 sanitizer 名称的结果 entry 添加set A

  4. 对于 B 中的每个 entry,将 规范化 sanitizer 名称的结果 entry 添加set B

  5. 返回 set Aset B交集

判断一个布尔值 boolnot(非),如果 bool 为 true,则返回 false,否则返回 true。
注释包含对算法中某处的解释性说明文本。

3.6. 内置项

有五个内置项:

内置安全默认配置 如下:

{
  "elements": [
    {
      "name": "math",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "merror",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mfrac",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mi",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mmultiscripts",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mn",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mo",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": [
        {
          "name": "fence",
          "namespace": null
        },
        {
          "name": "form",
          "namespace": null
        },
        {
          "name": "largeop",
          "namespace": null
        },
        {
          "name": "lspace",
          "namespace": null
        },
        {
          "name": "maxsize",
          "namespace": null
        },
        {
          "name": "minsize",
          "namespace": null
        },
        {
          "name": "movablelimits",
          "namespace": null
        },
        {
          "name": "rspace",
          "namespace": null
        },
        {
          "name": "separator",
          "namespace": null
        },
        {
          "name": "stretchy",
          "namespace": null
        },
        {
          "name": "symmetric",
          "namespace": null
        }
      ]
    },
    {
      "name": "mover",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": [
        {
          "name": "accent",
          "namespace": null
        }
      ]
    },
    {
      "name": "mpadded",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": [
        {
          "name": "depth",
          "namespace": null
        },
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "lspace",
          "namespace": null
        },
        {
          "name": "voffset",
          "namespace": null
        },
        {
          "name": "width",
          "namespace": null
        }
      ]
    },
    {
      "name": "mphantom",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mprescripts",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mroot",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mrow",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "ms",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mspace",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": [
        {
          "name": "depth",
          "namespace": null
        },
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "width",
          "namespace": null
        }
      ]
    },
    {
      "name": "msqrt",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mstyle",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "msub",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "msubsup",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "msup",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mtable",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mtd",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": [
        {
          "name": "columnspan",
          "namespace": null
        },
        {
          "name": "rowspan",
          "namespace": null
        }
      ]
    },
    {
      "name": "mtext",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "mtr",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "munder",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": [
        {
          "name": "accentunder",
          "namespace": null
        }
      ]
    },
    {
      "name": "munderover",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": [
        {
          "name": "accent",
          "namespace": null
        },
        {
          "name": "accentunder",
          "namespace": null
        }
      ]
    },
    {
      "name": "semantics",
      "namespace": "http://www.w3.org/1998/Math/MathML",
      "attributes": []
    },
    {
      "name": "a",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "href",
          "namespace": null
        },
        {
          "name": "hreflang",
          "namespace": null
        },
        {
          "name": "type",
          "namespace": null
        }
      ]
    },
    {
      "name": "abbr",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "address",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "article",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "aside",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "b",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "bdi",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "bdo",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "blockquote",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "cite",
          "namespace": null
        }
      ]
    },
    {
      "name": "body",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "br",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "caption",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "cite",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "code",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "col",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "span",
          "namespace": null
        }
      ]
    },
    {
      "name": "colgroup",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "span",
          "namespace": null
        }
      ]
    },
    {
      "name": "data",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "value",
          "namespace": null
        }
      ]
    },
    {
      "name": "dd",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "del",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "cite",
          "namespace": null
        },
        {
          "name": "datetime",
          "namespace": null
        }
      ]
    },
    {
      "name": "dfn",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "div",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "dl",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "dt",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "em",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "figcaption",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "figure",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "footer",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "h1",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "h2",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "h3",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "h4",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "h5",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "h6",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "head",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "header",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "hgroup",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "hr",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "html",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "i",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "ins",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "cite",
          "namespace": null
        },
        {
          "name": "datetime",
          "namespace": null
        }
      ]
    },
    {
      "name": "kbd",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "li",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "value",
          "namespace": null
        }
      ]
    },
    {
      "name": "main",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "mark",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "menu",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "nav",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "ol",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "reversed",
          "namespace": null
        },
        {
          "name": "start",
          "namespace": null
        },
        {
          "name": "type",
          "namespace": null
        }
      ]
    },
    {
      "name": "p",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "pre",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "q",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "rp",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "rt",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "ruby",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "s",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "samp",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "search",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "section",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "small",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "span",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "strong",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "sub",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "sup",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "table",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "tbody",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "td",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "colspan",
          "namespace": null
        },
        {
          "name": "headers",
          "namespace": null
        },
        {
          "name": "rowspan",
          "namespace": null
        }
      ]
    },
    {
      "name": "tfoot",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "th",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "abbr",
          "namespace": null
        },
        {
          "name": "colspan",
          "namespace": null
        },
        {
          "name": "headers",
          "namespace": null
        },
        {
          "name": "rowspan",
          "namespace": null
        },
        {
          "name": "scope",
          "namespace": null
        }
      ]
    },
    {
      "name": "thead",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "time",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "datetime",
          "namespace": null
        }
      ]
    },
    {
      "name": "title",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "tr",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "u",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "ul",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "var",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "wbr",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "a",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "href",
          "namespace": null
        },
        {
          "name": "hreflang",
          "namespace": null
        },
        {
          "name": "type",
          "namespace": null
        }
      ]
    },
    {
      "name": "circle",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "cx",
          "namespace": null
        },
        {
          "name": "cy",
          "namespace": null
        },
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "r",
          "namespace": null
        }
      ]
    },
    {
      "name": "defs",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "desc",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "ellipse",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "cx",
          "namespace": null
        },
        {
          "name": "cy",
          "namespace": null
        },
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "rx",
          "namespace": null
        },
        {
          "name": "ry",
          "namespace": null
        }
      ]
    },
    {
      "name": "foreignObject",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "width",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        }
      ]
    },
    {
      "name": "g",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "line",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "x1",
          "namespace": null
        },
        {
          "name": "x2",
          "namespace": null
        },
        {
          "name": "y1",
          "namespace": null
        },
        {
          "name": "y2",
          "namespace": null
        }
      ]
    },
    {
      "name": "marker",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "markerHeight",
          "namespace": null
        },
        {
          "name": "markerUnits",
          "namespace": null
        },
        {
          "name": "markerWidth",
          "namespace": null
        },
        {
          "name": "orient",
          "namespace": null
        },
        {
          "name": "preserveAspectRatio",
          "namespace": null
        },
        {
          "name": "refX",
          "namespace": null
        },
        {
          "name": "refY",
          "namespace": null
        },
        {
          "name": "viewBox",
          "namespace": null
        }
      ]
    },
    {
      "name": "metadata",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "path",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "d",
          "namespace": null
        },
        {
          "name": "pathLength",
          "namespace": null
        }
      ]
    },
    {
      "name": "polygon",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "points",
          "namespace": null
        }
      ]
    },
    {
      "name": "polyline",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "points",
          "namespace": null
        }
      ]
    },
    {
      "name": "rect",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "rx",
          "namespace": null
        },
        {
          "name": "ry",
          "namespace": null
        },
        {
          "name": "width",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        }
      ]
    },
    {
      "name": "svg",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "preserveAspectRatio",
          "namespace": null
        },
        {
          "name": "viewBox",
          "namespace": null
        },
        {
          "name": "width",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        }
      ]
    },
    {
      "name": "text",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "dx",
          "namespace": null
        },
        {
          "name": "dy",
          "namespace": null
        },
        {
          "name": "lengthAdjust",
          "namespace": null
        },
        {
          "name": "rotate",
          "namespace": null
        },
        {
          "name": "textLength",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        }
      ]
    },
    {
      "name": "textPath",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "lengthAdjust",
          "namespace": null
        },
        {
          "name": "method",
          "namespace": null
        },
        {
          "name": "path",
          "namespace": null
        },
        {
          "name": "side",
          "namespace": null
        },
        {
          "name": "spacing",
          "namespace": null
        },
        {
          "name": "startOffset",
          "namespace": null
        },
        {
          "name": "textLength",
          "namespace": null
        }
      ]
    },
    {
      "name": "title",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "tspan",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "dx",
          "namespace": null
        },
        {
          "name": "dy",
          "namespace": null
        },
        {
          "name": "lengthAdjust",
          "namespace": null
        },
        {
          "name": "rotate",
          "namespace": null
        },
        {
          "name": "textLength",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        }
      ]
    }
  ],
  "processingInstructions": [],
  "attributes": [
    {
      "name": "alignment-baseline",
      "namespace": null
    },
    {
      "name": "baseline-shift",
      "namespace": null
    },
    {
      "name": "clip-path",
      "namespace": null
    },
    {
      "name": "clip-rule",
      "namespace": null
    },
    {
      "name": "color",
      "namespace": null
    },
    {
      "name": "color-interpolation",
      "namespace": null
    },
    {
      "name": "cursor",
      "namespace": null
    },
    {
      "name": "dir",
      "namespace": null
    },
    {
      "name": "direction",
      "namespace": null
    },
    {
      "name": "display",
      "namespace": null
    },
    {
      "name": "displaystyle",
      "namespace": null
    },
    {
      "name": "dominant-baseline",
      "namespace": null
    },
    {
      "name": "fill",
      "namespace": null
    },
    {
      "name": "fill-opacity",
      "namespace": null
    },
    {
      "name": "fill-rule",
      "namespace": null
    },
    {
      "name": "font-family",
      "namespace": null
    },
    {
      "name": "font-size",
      "namespace": null
    },
    {
      "name": "font-size-adjust",
      "namespace": null
    },
    {
      "name": "font-stretch",
      "namespace": null
    },
    {
      "name": "font-style",
      "namespace": null
    },
    {
      "name": "font-variant",
      "namespace": null
    },
    {
      "name": "font-weight",
      "namespace": null
    },
    {
      "name": "lang",
      "namespace": null
    },
    {
      "name": "letter-spacing",
      "namespace": null
    },
    {
      "name": "marker-end",
      "namespace": null
    },
    {
      "name": "marker-mid",
      "namespace": null
    },
    {
      "name": "marker-start",
      "namespace": null
    },
    {
      "name": "mathbackground",
      "namespace": null
    },
    {
      "name": "mathcolor",
      "namespace": null
    },
    {
      "name": "mathsize",
      "namespace": null
    },
    {
      "name": "opacity",
      "namespace": null
    },
    {
      "name": "paint-order",
      "namespace": null
    },
    {
      "name": "pointer-events",
      "namespace": null
    },
    {
      "name": "scriptlevel",
      "namespace": null
    },
    {
      "name": "shape-rendering",
      "namespace": null
    },
    {
      "name": "stop-color",
      "namespace": null
    },
    {
      "name": "stop-opacity",
      "namespace": null
    },
    {
      "name": "stroke",
      "namespace": null
    },
    {
      "name": "stroke-dasharray",
      "namespace": null
    },
    {
      "name": "stroke-dashoffset",
      "namespace": null
    },
    {
      "name": "stroke-linecap",
      "namespace": null
    },
    {
      "name": "stroke-linejoin",
      "namespace": null
    },
    {
      "name": "stroke-miterlimit",
      "namespace": null
    },
    {
      "name": "stroke-opacity",
      "namespace": null
    },
    {
      "name": "stroke-width",
      "namespace": null
    },
    {
      "name": "text-anchor",
      "namespace": null
    },
    {
      "name": "text-decoration",
      "namespace": null
    },
    {
      "name": "text-overflow",
      "namespace": null
    },
    {
      "name": "text-rendering",
      "namespace": null
    },
    {
      "name": "title",
      "namespace": null
    },
    {
      "name": "transform",
      "namespace": null
    },
    {
      "name": "transform-origin",
      "namespace": null
    },
    {
      "name": "unicode-bidi",
      "namespace": null
    },
    {
      "name": "vector-effect",
      "namespace": null
    },
    {
      "name": "visibility",
      "namespace": null
    },
    {
      "name": "white-space",
      "namespace": null
    },
    {
      "name": "word-spacing",
      "namespace": null
    },
    {
      "name": "writing-mode",
      "namespace": null
    }
  ],
  "comments": false,
  "dataAttributes": false
}

注意:所包含的 [MathML] 标记基于 [SafeMathML]

内置安全基线配置旨在仅阻止脚本内容。具体如下:

{
  "removeElements": [
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "embed"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "frame"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "iframe"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "object"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "script"
    },
    {
      "namespace": "http://www.w3.org/2000/svg",
      "name": "script"
    },
    {
      "namespace": "http://www.w3.org/2000/svg",
      "name": "use"
    }
  ],
  "removeAttributes": []
}

警告: remove unsafe 算法还规定要额外移除所有 事件处理内容属性,详见 [HTML]。 如果某个 用户代理[HTML] 规范基础上扩展了额外的 事件处理内容属性,则如何处理这些属性由其自行决定。基于当前的 事件处理内容属性 列表, 安全基线配置实际上大致如下所示:

{
  "removeElements": [
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "embed"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "frame"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "iframe"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "object"
    },
    {
      "namespace": "http://www.w3.org/1999/xhtml",
      "name": "script"
    },
    {
      "namespace": "http://www.w3.org/2000/svg",
      "name": "script"
    },
    {
      "namespace": "http://www.w3.org/2000/svg",
      "name": "use"
    }
  ],
  "removeAttributes": [
    "onafterprint",
    "onauxclick",
    "onbeforeinput",
    "onbeforematch",
    "onbeforeprint",
    "onbeforeunload",
    "onbeforetoggle",
    "onblur",
    "oncancel",
    "oncanplay",
    "oncanplaythrough",
    "onchange",
    "onclick",
    "onclose",
    "oncontextlost",
    "oncontextmenu",
    "oncontextrestored",
    "oncopy",
    "oncuechange",
    "oncut",
    "ondblclick",
    "ondrag",
    "ondragend",
    "ondragenter",
    "ondragleave",
    "ondragover",
    "ondragstart",
    "ondrop",
    "ondurationchange",
    "onemptied",
    "onended",
    "onerror",
    "onfocus",
    "onformdata",
    "onhashchange",
    "oninput",
    "oninvalid",
    "onkeydown",
    "onkeypress",
    "onkeyup",
    "onlanguagechange",
    "onload",
    "onloadeddata",
    "onloadedmetadata",
    "onloadstart",
    "onmessage",
    "onmessageerror",
    "onmousedown",
    "onmouseenter",
    "onmouseleave",
    "onmousemove",
    "onmouseout",
    "onmouseover",
    "onmouseup",
    "onoffline",
    "ononline",
    "onpagehide",
    "onpagereveal",
    "onpageshow",
    "onpageswap",
    "onpaste",
    "onpause",
    "onplay",
    "onplaying",
    "onpopstate",
    "onprogress",
    "onratechange",
    "onreset",
    "onresize",
    "onrejectionhandled",
    "onscroll",
    "onscrollend",
    "onsecuritypolicyviolation",
    "onseeked",
    "onseeking",
    "onselect",
    "onslotchange",
    "onstalled",
    "onstorage",
    "onsubmit",
    "onsuspend",
    "ontimeupdate",
    "ontoggle",
    "onunhandledrejection",
    "onunload",
    "onvolumechange",
    "onwaiting",
    "onwheel"
  ]
}
内置导航 URL 属性列表,其中 "javascript:" 导航被视为“不安全”,内容如下:

«[
[ { "name" → "a", "namespace" → HTML 命名空间 }, { "name" → "href", "namespace" → null } ],
[ { "name" → "area", "namespace" → HTML 命名空间 }, { "name" → "href", "namespace" → null } ],
[ { "name" → "base", "namespace" → HTML 命名空间 }, { "name" → "href", "namespace" → null } ],
[ { "name" → "button", "namespace" → HTML 命名空间 }, { "name" → "formaction", "namespace" → null } ],
[ { "name" → "form", "namespace" → HTML 命名空间 }, { "name" → "action", "namespace" → null } ],
[ { "name" → "input", "namespace" → HTML 命名空间 }, { "name" → "formaction", "namespace" → null } ],
[ { "name" → "a", "namespace" → SVG 命名空间 }, { "name" → "href", "namespace" → null } ],
[ { "name" → "a", "namespace" → SVG 命名空间 }, { "name" → "href", "namespace" → XLink 命名空间 } ],

内置动画型 URL 属性列表,可用于 [SVG11] 规范中,通过声明式方式修改导航元素以使用 "javascript:" URL,其内容如下:

«[
[ { "name" → "animate", "namespace" → SVG 命名空间 }, { "name" → "attributeName", "namespace" → null] } ],
[ { "name" → "animateTransform", "namespace" → SVG 命名空间 }, { "name" → "attributeName", "namespace" → null } ],
[ { "name" → "set", "namespace" → SVG 命名空间 }, { "name" → "attributeName", "namespace" → null } ],

内置不可替换元素列表包含一些元素,这些元素不能用它们的子元素替换,否则可能导致解析问题或无效的节点树。

«[
{ "name" → "html", "namespace" → HTML 命名空间 },
{ "name" → "svg", "namespace" → SVG 命名空间 },
{ "name" → "math", "namespace" → MathML 命名空间 },

注意:

这些限制不足以防止所有类型的重新解析攻击,例如:

// 错误示例
let sanitizer = { replaceWithChildrenElements: ["tr"] };
div.setHTML("<table><tbody><tr><td><div>", { sanitizer });

div.innerHTML = div.innerHTML;
// div.innerHTML 现在是 `<div></div><table><tbody><tr></tr></tbody></table>`

不过,这样可以防止本例中的 <iframe> 被重新解析并变成普通 HTML iframe:

let sanitizer = { replaceWithChildrenElements: [
    { name: "svg", namespace: "http://www.w3.org/2000/svg"}
  ]
};
div.setHTML(`<svg><iframe src="https://example.com"></iframe></svg>`, { sanitizer }); // 抛出异常

4. 安全注意事项

Sanitizer API 旨在通过遍历所提供的 HTML 内容,并根据配置移除元素和属性,防止基于 DOM 的跨站脚本(XSS)攻击。规范要求,API 不应支持构造会遗留可执行脚本标记的 Sanitizer 对象,否则将会破坏威胁模型。

但即便如此,正确使用 Sanitizer API 也无法防护所有安全问题,相关场景会在下文中说明。

4.1. 服务端反射型和存储型 XSS

本节为非规范性内容。

Sanitizer API 仅在 DOM 中操作,提供遍历和过滤现有 DocumentFragment 的能力。Sanitizer 不涉及服务端反射型或存储型 XSS 问题。

4.2. DOM 覆写攻击(clobbering)

本节为非规范性内容。

DOM 覆写攻击指通过恶意 HTML 使用 idname 属性命名元素,从而让 HTML 元素的某些属性(如 children)被恶意内容覆盖,导致应用混淆。

Sanitizer API 的默认行为无法防护 DOM 覆写攻击,但可以通过配置移除 idname 属性。

4.3. 利用脚本组件的 XSS

本节为非规范性内容。

脚本组件攻击是一种攻击者利用流行 JavaScript 库中的已有应用代码,通过注入看似无害的代码或仅被框架解析的 DOM 节点,最终由框架解析并执行这些输入中的 JavaScript 的技术。

Sanitizer API 无法防止此类攻击,要求页面作者一般要显式允许未知元素,并且还需针对未知属性、元素及如 data-slot 属性和 <slot><template> 等广泛用于模板和框架的标记作出明确配置。 我们认为这些限制并不详尽,鼓励页面作者检查所用第三方库是否有此类行为。

4.4. 变异型 XSS(mXSS)

本节为非规范性内容。

变异型 XSS(mXSS)描述的是由于解析器上下文不一致,在错误上下文下解析 HTML 片段时发生攻击。当某个已解析的 HTML 片段序列化为字符串后,在插入到不同父元素时未必能保证解析效果一致。例如,依赖于外部内容或标签嵌套错误导致解析行为变化,即可发动此类攻击。

Sanitizer API 只提供将字符串转为节点树的功能。上下文由所有 sanitizer 函数隐式提供:Element.setHTML() 使用当前元素;Document.parseHTML() 创建新文档。因此 Sanitizer API 不会直接受到变异型 XSS 影响。

如果开发者将净化后的节点树通过 .innerHTML 取字符串,再重新解析,则有可能发生变异型 XSS。我们不推荐这种做法。如果确实需要处理或传递 HTML 字符串,则应将其视为不可信内容,并在插入 DOM 时重新净化。换言之,净化并序列化后的 HTML 树不应再被视作已净化。

关于 mXSS 的更完整讨论可见 [MXSS]

5. 致谢

本项工作借鉴了 cure53 团队的 [DOMPURIFY], 以及 Internet Explorer 的 window.toStaticHTML() 和 Ben Bucksch 的 [HTMLSanitizer]。感谢 Anne van Kesteren、Krzysztof Kotowicz、 Andrew C. H. Mc Millan、Tom Schuster、Luke Warlow、Guillaume Weghsteen 以及 Mike West 的宝贵反馈。

索引

本规范定义的术语

引用中定义的术语

参考文献

规范性引用

[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; 等. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL: https://infra.spec.whatwg.org/
[TRUSTED-TYPES]
Krzysztof Kotowicz. Trusted Types. URL: https://w3c.github.io/trusted-types/dist/spec/
[URL]
Anne van Kesteren. URL 标准. 现行标准. URL: https://url.spec.whatwg.org/
[WebIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. 现行标准. URL: https://webidl.spec.whatwg.org/

参考性引用

[DOMPURIFY]
DOMPurify. URL: https://github.com/cure53/DOMPurify
[HTMLSanitizer]
HTML Sanitizer. URL: https://www.bucksch.org/1/projects/mozilla/108153/
[MathML]
Patrick D F Ion; Robert R Miner. 数学标记语言 (MathML™) 1.01 规范. 2023年3月7日. REC. URL: https://www.w3.org/TR/REC-MathML/
[MXSS]
mXSS 攻击:通过 innerHTML 变异攻击安全性良好的 web 应用. URL: https://cure53.de/fp170.pdf
[SafeMathML]
MathML 安全列表. URL: https://w3c.github.io/mathml-docs/mathml-safe-list
[SVG11]
Erik Dahlström; 等. 可缩放矢量图形 (SVG) 1.1(第二版). 2011年8月16日. REC. URL: https://www.w3.org/TR/SVG11/

IDL 索引

partial interface Element {
  [CEReactions] undefined setHTML(DOMString html, optional SetHTMLOptions options = {});
};

partial interface ShadowRoot {
  [CEReactions] undefined setHTML(DOMString html, optional SetHTMLOptions options = {});
};

partial interface Document {
  static Document parseHTML(DOMString html, optional SetHTMLOptions options = {});
};

enum SanitizerPresets { "default" };
dictionary SetHTMLOptions {
  (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = "default";
};
dictionary SetHTMLUnsafeOptions {
  (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = {};
};

[Exposed=Window]
interface Sanitizer {
  constructor(optional (SanitizerConfig or SanitizerPresets) configuration = "default");

  // Query configuration:
  SanitizerConfig get();

  // Modify a Sanitizer's lists and fields:
  boolean allowElement(SanitizerElementWithAttributes element);
  boolean removeElement(SanitizerElement element);
  boolean replaceElementWithChildren(SanitizerElement element);
  boolean allowProcessingInstruction(SanitizerPI pi);
  boolean removeProcessingInstruction(SanitizerPI pi);
  boolean allowAttribute(SanitizerAttribute attribute);
  boolean removeAttribute(SanitizerAttribute attribute);
  boolean setComments(boolean allow);
  boolean setDataAttributes(boolean allow);

  // Remove markup that executes script.
  boolean removeUnsafe();
};

dictionary SanitizerElementNamespace {
  required DOMString name;
  DOMString? _namespace = "http://www.w3.org/1999/xhtml";
};

// Used by "elements"
dictionary SanitizerElementNamespaceWithAttributes : SanitizerElementNamespace {
  sequence<SanitizerAttribute> attributes;
  sequence<SanitizerAttribute> removeAttributes;
};

typedef (DOMString or SanitizerElementNamespace) SanitizerElement;
typedef (DOMString or SanitizerElementNamespaceWithAttributes) SanitizerElementWithAttributes;

dictionary SanitizerProcessingInstruction {
  required DOMString target;
};

typedef (DOMString or SanitizerProcessingInstruction) SanitizerPI;

dictionary SanitizerAttributeNamespace {
  required DOMString name;
  DOMString? _namespace = null;
};
typedef (DOMString or SanitizerAttributeNamespace) SanitizerAttribute;

dictionary SanitizerConfig {
  sequence<SanitizerElementWithAttributes> elements;
  sequence<SanitizerElement> removeElements;
  sequence<SanitizerElement> replaceWithChildrenElements;

  sequence<SanitizerPI> processingInstructions;
  sequence<SanitizerPI> removeProcessingInstructions;

  sequence<SanitizerAttribute> attributes;
  sequence<SanitizerAttribute> removeAttributes;

  boolean comments;
  boolean dataAttributes;
};