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 平台孵化社区组 发布。 它既不是 W3C 标准,亦不在 W3C 标准化流程上。 请注意,根据 W3C 社区贡献者许可协议 (CLA) 存在有限的选择退出,并且适用其他条件。 了解更多关于 W3C 社区与业务团体 的信息。

1. 介绍

本节不具备规范性。

Web 应用通常需要在客户端处理 HTML 字符串, 可能用在客户端模板解决方案,也可能用于渲染用户生成的内容等场景。安全地处理这些字符串非常困难。 简单地拼接字符串并插入到 ElementinnerHTML 属性中,这种做法风险极高,因为可能会以各种意想不到的方式导致 JavaScript 执行。

[DOMPURIFY] 这样的库 试图通过在插入前仔细解析和净化字符串来解决这个问题,方法是构建 DOM 并根据允许列表过滤成员。 但这种做法很脆弱,Web 暴露的解析 API 与浏览器实际渲染字符串为 HTML 时的行为并不总能合理对应。 此外,这些库还需要不断跟踪浏览器行为的变化;曾经安全的做法可能因新平台特性变得危险。

浏览器本身对何时执行代码有相当清晰的判断。 我们可以通过让浏览器自身以安全方式渲染任意 HTML 字符串,从而改进用户空间库, 并且这种方式更有可能与浏览器解析器的持续更新保持同步。 本文档描述了一个旨在实现这一目标的 API。

1.1. 目标

1.2. API 概述

Sanitizer API 提供将包含 HTML 的字符串解析为 DOM 树,并根据用户配置过滤结果树的功能。 方法有两种风格:

2. 框架

2.1. Sanitizer API

Element 接口定义了两个方法,setHTML()setHTMLUnsafe()。 这两个方法都接受带有 HTML 标记的 DOMString, 以及可选的配置项。

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

  2. targetthistemplate contents(如果 thistemplate 元素);否则为 this

  3. 根据 targetthiscompliantHTMLoptions 和 false 调用 设置并过滤 HTML

ElementsetHTML(html, options) 方法步骤如下:
  1. targetthistemplate contents(如果 thistemplate 元素);否则为 this

  2. 根据 targetthishtmloptions 和 true 调用 设置并过滤 HTML

partial interface ShadowRoot {
  [CEReactions] undefined setHTMLUnsafe((TrustedHTML or DOMString) html, optional SetHTMLUnsafeOptions options = {});
  [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 = {});
  static Document parseHTML(DOMString html, optional SetHTMLOptions options = {});
};
parseHTMLUnsafe(html, options) 方法步骤如下:
  1. compliantHTML 为调用 获取受信类型兼容字符串 算法的结果, 参数为 TrustedHTMLthis相关全局对象html、"Document parseHTMLUnsafe" 和 "script"。

  2. document 为新的 Document, 其 内容类型 为 "text/html"。

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

  3. 设置 document允许声明式 shadow root 为 true。

  4. 根据 documentcompliantHTML从字符串解析 HTML

  5. sanitizer 为调用 根据 options 获取 sanitizier 实例 的结果,参数为 options 和 false。

  6. document 上调用 sanitize,参数为 sanitizer 和 false。

  7. 返回 document

parseHTML(html, options) 方法步骤如下:
  1. document 为新的 Document, 其 内容类型 为 "text/html"。

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

  2. 设置 document允许声明式 shadow root 为 true。

  3. 根据 documenthtml从字符串解析 HTML

  4. sanitizer 为调用 根据 options 获取 sanitizier 实例 的结果,参数为 options 和 true。

  5. document 上调用 sanitize,参数为 sanitizer 和 true。

  6. 返回 document

2.2. SetHTML 选项和配置对象

所有 setHTML() 类方法都接受一个 options 字典。当前只定义了字典的一个成员:

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");

  // 查询配置:
  SanitizerConfig get();

  // 修改 Sanitizer 的列表和字段:
  boolean allowElement(SanitizerElementWithAttributes element);
  boolean removeElement(SanitizerElement element);
  boolean replaceElementWithChildren(SanitizerElement element);
  boolean allowAttribute(SanitizerAttribute attribute);
  boolean removeAttribute(SanitizerAttribute attribute);
  boolean setComments(boolean allow);
  boolean setDataAttributes(boolean allow);

  // 移除可执行脚本的标记
  boolean removeUnsafe();
};

Sanitizer 具有一个关联的 SanitizerConfig configuration(配置)。

constructor(configuration) 方法步骤如下:
  1. 如果 configurationSanitizerPresets 字符串,则:

    1. 断言configuration default

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

  2. valid 为在 set a configuration 中 传入 configuration 和 true,作用于 this 的返回值。

  3. 如果 valid 为 false,则抛出 TypeError

get() 方法步骤为:返回 thisconfiguration
allowElement(element) 方法步骤为, 使用 elementthisconfiguration, 调用 allow an element
removeElement(element) 方法步骤为, 使用 elementthisconfiguration, 调用 remove an element
replaceElementWithChildren(element) 方法步骤为, 使用 elementthisconfiguration, 调用 replace an element with its children
allowAttribute(attribute) 方法步骤为, 使用 attributethisconfiguration, 调用 allow an attribute
removeAttribute(attribute) 方法步骤为, 使用 attributethisconfiguration, 调用 remove an attribute
setComments(allow) 方法步骤为, 使用 allowthisconfiguration, 调用 set comments
setDataAttributes(allow) 方法步骤为, 使用 allowthisconfiguration, 调用 set data attributes
removeUnsafe() 方法步骤为, 用 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 SanitizerAttributeNamespace {
  required DOMString name;
  DOMString? _namespace = null;
};
typedef (DOMString or SanitizerAttributeNamespace) SanitizerAttribute;

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

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

  boolean comments;
  boolean dataAttributes;
};

2.4. 配置不变量

配置可以也应当由开发者根据实际需求进行修改。可以选择直接新建一个配置字典,也可以通过修饰方法修改已存在的Sanitizer的配置,或者用get()获取现有Sanitizer配置字典,修改后再创建新的Sanitizer

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

并非所有配置字典都是有效的。有效的配置避免冗余(如两次允许同一个元素)和矛盾(如同一个元素既被允许又被移除)。

一个有效配置需要满足以下多个条件:

elements 元素允许列表也可以为某个元素指定允许或移除的属性。这与[HTML]的结构类似,HTML区分了全局属性和只适用于特定元素的局部属性。全局和局部属性可混合使用,但如果某个属性在一个列表允许,在另一个列表禁止,则该配置一般是无效的。

全局 attributes 全局 removeAttributes
局部 attributes 属性只要在任一列表中匹配就被允许。不允许重复项。 属性只有在局部允许列表中才被允许。全局移除与局部允许之间不能有重复。注意全局移除列表对该元素没有作用,但可能对没有局部允许列表的其它元素有作用。
局部 removeAttributes 属性只要在全局允许列表且不在局部移除列表就被允许。局部移除必须是全局允许列表的子集。 属性只有在两个列表都没有时才被允许。全局移除与局部移除之间不能有重复项。

请注意,大多数情况下全局与局部列表之间都不能有重复项,但当存在全局允许列表和局部移除列表时,局部移除列表必须是全局允许列表的子集。下面摘录了上表关于重复项的部分:

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

dataAttributes 设置允许自定义 data 属性。如将dataAttributes 视为允许列表,上述规则可直接扩展到自定义 data 属性

全局 attributesdataAttributes 已配置
局部 attributes 所有自定义 data 属性都被允许。不允许任何自定义 data 属性出现在任何允许列表,否则会出现重复项。
局部 removeAttributes 自定义 data 属性会被允许,除非它在局部移除列表中。 不允许任何自定义 data 属性出现在全局允许列表,否则会出现重复项。

用文字总结这些规则:

一个 SanitizerConfig config有效的,当且仅当以下所有条件成立:
  1. config 要么有 elements 键,要么有 removeElements ,但不能两者都有。

  2. config 要么有 attributes 键,要么有 removeAttributes ,但不能两者都有。

  3. 断言config 中所有 SanitizerElementNamespaceWithAttributesSanitizerElementNamespace, 以及 SanitizerAttributeNamespace 项都是规范化的,即它们已根据需要经过 规范化元素规范化属性 过程处理。

  4. 如果 config[elements]、 config[removeElements]、 config[replaceWithChildrenElements]、 config[attributes], 或 config[removeAttributes] 存在, 则它们都不应 有重复项

  5. 如果 config[elements] 和 config[replaceWithChildrenElements] 都存在,则 两者的交集必须是 空的

  6. 如果 config[removeElements] 和 config[replaceWithChildrenElements] 都存在,则 两者的交集必须是 空的

  7. 如果 config[attributes] 存在

    1. 如果 config[elements] 存在

      1. 对于任意 element 属于 config[elements]:

        1. element[attributes] 和 element[removeAttributes], 如果存在,都不得 有重复项

        2. config[attributes] 与 element[attributes] 默认值为 « [] » 时,两者 交集为空

        3. element[removeAttributes] 默认值 « [] » 是 config[attributes] 的子集

        4. 如果 dataAttributes 存在dataAttributes 为 true:

          1. element[attributes] 不包含 自定义数据属性

    2. 如果 dataAttributes 为 true:

      1. config[attributes] 不包含 自定义数据属性

  8. 如果 config[removeAttributes] 存在

    1. 如果 config[elements] 存在, 则 对于任意 element 属于 config[elements]:

      1. config[removeAttributes] 与 element[attributes] 默认值 « [] » 时,两者交集是 空的

      2. config[removeAttributes] 与 element[removeAttributes] 默认值 « [] » 时,两者交集是 空的

    2. config[dataAttributes] 不 存在

注意:字典设置配置时会做一定的归一化处理。特别地,如果允许列表和移除列表都缺失,则会解释为一个空移除列表。所以{}本身不是有效配置,但会归一化为{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. newChildrenHTML 片段解析算法的结果, 参数为 contextElementhtml 和 true。

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

  5. 对于每个 node 属于 newChildren追加 nodefragment

  6. fragment 上运行 sanitize, 使用 sanitizersafe

  7. fragment 替换全部 target 内的内容。

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

注意:此算法适用于 SetHTMLOptionsSetHTMLUnsafeOptions。 它们仅在默认值上有所区别。

  1. sanitizerSpec 为 "default"。

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

    1. sanitizerSpec 设为 options["sanitizer"]

  3. 断言sanitizerSpec 必须是 Sanitizer 实例、 属于 字符串并是 SanitizerPresets 成员,或一个字典

  4. 如果 sanitizerSpec字符串

    1. 断言sanitizerSpec "default"

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

  5. 断言sanitizerSpec 必须是 Sanitizer 实例或字典

  6. 如果 sanitizerSpec字典

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

    2. setConfigurationResult 为在 sanitizer 上调用 set a configuration ,参数为 sanitizerSpec safe

    3. 如果 setConfigurationResult 为 false,抛出 TypeError

    4. sanitizerSpec 设为 sanitizer

  7. 断言sanitizerSpec 必须是 Sanitizer 实例。

  8. 返回 sanitizerSpec

3.1. 净化

sanitize 操作,使用一个 ParentNode node, 一个 Sanitizer sanitizer,以及一个 boolean safe,运行以下步骤:
  1. configurationsanitizerconfiguration 的值。

  2. 如果 safe 为 true,则将 configuration 设为调用 remove unsafeconfiguration 的结果。

  3. 调用 sanitize corenodeconfiguration,并将 handleJavascriptNavigationUrls 设为 safe

sanitize core 操作, 使用一个 ParentNode node,一个 SanitizerConfig configuration,以及一个 boolean handleJavascriptNavigationUrls,从 node 开始递归遍历 DOM 树。步骤如下:
  1. 对每个 child 属于 node子节点

    1. 断言child 必须 实现 TextCommentElement、 或 DocumentType

      注意: 当前此算法仅用于 HTML 解析器输出,断言应成立。DocumentType 只会在 parseHTMLparseHTMLUnsafe 过程中出现。若将来算法适用范围扩大,应重新评估此断言。

    2. 如果 child 实现 DocumentType, 则 继续

    3. 如果 child 实现 Text, 则 继续

    4. 如果 child 实现 Comment

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

    5. 否则:

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

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

        1. 调用 sanitize corechild, 参数为 configurationhandleJavascriptNavigationUrls

        2. 调用 replace all,用 child子节点 替换 child

        3. 继续

      3. 如果 configuration["removeElements"] 存在configuration["removeElements"] 包含 elementName

        1. 移除 child

        2. 继续

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

        1. 移除 child

        2. 继续

      5. 如果 elementName 等于 «[ "name" → "template", "namespace" → HTML 命名空间 ]», 则调用 sanitize corechildtemplate contents,参数为 configurationhandleJavascriptNavigationUrls

      6. 如果 childshadow host, 则调用 sanitize corechildshadow root,参数为 configurationhandleJavascriptNavigationUrls

      7. elementWithLocalAttributes 为 « [] »。

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

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

      9. 对每个 attribute 属于 child属性列表

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

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

          1. 移除 attribute

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

          1. 如果 configuration["attributes"] 不包含 attrNameelementWithLocalAttributes["attributes"] 默认 « [] » 不包含 attrName,且 "data-" 不是 码元前缀attribute本地名称命名空间不为 nullconfiguration["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命名空间nullXLink 命名空间, 且 attr 包含 javascript: URL, 则 移除 attribute

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

      10. 调用 sanitize corechild, 参数为 configurationhandleJavascriptNavigationUrls

注意: 当前浏览器只在导航时支持 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 为运行 基本URL解析器attribute 的结果。

  2. 如果 urlfailure,则返回 false。

  3. 返回 urlscheme "javascript"。

3.2. 修改配置

配置修饰方法是定义在 Sanitizer 上的用于修改其配置的方法。 它们会维护配置的有效性条件。 它们返回一个布尔值,告知调用方配置是否被修改。

let s = new Sanitizer({elements: ["div"]});
s.allowElement("p"); // Returns true.
div.setHTML("<div><p>", {sanitizer: s});  // Allows `<div>` and `<p>`.
let s = new Sanitizer({elements: ["div"]});
s.removeElement("p");  // Return false, as <p> was not previously allowed.
div.setHTML("<div><p>", {sanitizer: s});  // Allows `<div>`. `<p>` is removed.
允许某个元素 SanitizerElementWithAttributes element 在一个 SanitizerConfig configuration 中执行以下操作:
注意: 该算法相对复杂,因为元素允许列表可以为每个元素指定局部的允许或移除属性列表。这要求我们区分 4 种情况:
  • 我们是否有全局的允许列表或移除列表,和

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

  1. element 设为 规范化带属性的清洗器元素 的结果, 以 element 作为参数。

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

    1. modified 设为 移除 elementconfiguration["replaceWithChildrenElements"] 的结果。

    2. 注释:需要确保每个元素的属性不与全局属性重叠。

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

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

        1. element["attributes"] 设为 去重后的 element["attributes"]。

        2. element["attributes"] 设为 差集, 以 element["attributes"] 和 configuration["attributes"] 作为输入。

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

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

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

        1. element["attributes"] 设为 差集, 以 element["attributes"] 和 configuration["removeAttributes"] 作为输入。

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

      1. element["removeAttributes"] 设为 去重后的 element["removeAttributes"]。

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

        1. element["removeAttributes"] 设为 交集, 以 element["removeAttributes"] 和 configuration["attributes"] 作为输入。

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

        1. element["removeAttributes"] 设为 差集, 以 element["removeAttributes"] 和 configuration["removeAttributes"] 作为输入。

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

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

      2. 追加 elementconfiguration["elements"]。

      3. 返回 true。

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

    7. current elementconfiguration["elements"] 中满足 item[name] 等于 element[name] 且 item[namespace] 等于 element[namespace] 的 item

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

    9. 移除 elementconfiguration["elements"]。

    10. 追加 elementconfiguration["elements"]

    11. 返回 true。

  3. 否则:

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

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

      2. 返回 false。

    2. modified 设为 移除 elementconfiguration["replaceWithChildrenElements"] 的结果。

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

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

      2. 返回 modified

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

    5. 移除 elementconfiguration["removeElements"]。

    6. 返回 true。

移除一个元素 SanitizerElement elementSanitizerConfig configuration 中:
注意: 此方法需要我们区分四种情况:
  • 我们有全局允许列表还是全局移除列表,

  • 它们是否已经包含 element

  1. element 设为 规范化清洗器元素 的结果,参数为 element

  2. modified 设为 移除 elementconfiguration["replaceWithChildrenElements"] 的结果。

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

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

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

      2. 移除 elementconfiguration["elements"]。

      3. 返回 true。

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

    3. 返回 modified

  4. 否则:

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

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

      2. 返回 modified

    2. 注释:我们有全局移除列表但未包含 element

    3. 添加 elementconfiguration["removeElements"]。

    4. 返回 true。

用其子节点替换一个元素 SanitizerElement elementSanitizerConfig configuration 中:
  1. element 设为 规范化清洗器元素 的结果,参数为 element

  2. 如果 configuration["replaceWithChildrenElements"] 包含 element

    1. 返回 false。

  3. 移除 elementconfiguration["removeElements"]。

  4. 移除 elementconfiguration["elements"] 列表中。

  5. 添加 elementconfiguration["replaceWithChildrenElements"]。

  6. 返回 true。

允许一个属性 SanitizerAttribute attributeSanitizerConfig configuration 上:

注意: 此方法区分两种情况,即我们有全局允许列表还是全局移除列表。如果将 attribute 添加到全局允许列表,可能需要额外操作以修复每元素的允许/移除列表以保持有效性。

  1. attribute 设为 规范化清洗器属性 的结果,参数为 attribute

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

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

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

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

    4. 注释:修复每元素的允许和移除列表。

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

      1. 遍历 configuration["elements"] 中的每个 element

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

          1. 移除 attributeelement["attributes"]。

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

    6. 追加 attributeconfiguration["attributes"]

    7. 返回 true。

  3. 否则:

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

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

      1. 返回 false。

    3. 移除 attributeconfiguration["removeAttributes"]。

    4. 返回 true。

移除一个属性 SanitizerAttributeattributeSanitizerConfig configuration 中:

注意: 此方法区分两种情况,即我们有全局允许列表还是全局移除列表。如果将 attribute 添加到全局移除列表,可能需要额外操作以修复每元素的允许/移除列表以保持有效性。如果从全局允许列表中移除 attribute,也可能需要从局部移除列表中移除它。

  1. attribute 设为 规范化清洗器属性 的结果,参数为 attribute

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

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

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

      1. 返回 false。

    3. 注释:修复每元素的允许和移除列表。

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

      1. 遍历 configuration["elements"] 中的每个 element

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

          1. 移除 attributeelement["removeAttributes"]。

    5. 移除 attributeconfiguration["attributes"]。

    6. 返回 true。

  3. 否则:

    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。

设置注释,用 布尔值 allow 作用于 SanitizerConfig configuration
  1. 如果 configuration["comments"] 存在configuration["comments"] 等于 allow,则返回 false;

  2. configuration["comments"] 设为 allow

  3. 返回 true。

设置 data 属性,用 布尔值 allow 作用于 SanitizerConfig configuration
  1. 如果 configuration["attributes"] 不 存在,则返回 false。

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

  3. 如果 allow 为 true:

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

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

      1. 遍历 configuration["elements"] 中的每个 element

        1. 如果 element[attributes] 存在

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

  4. configuration["dataAttributes"] 设为 allow

  5. 返回 true。

要从一个 SanitizerConfig configuration 中执行 remove unsafe,请执行以下步骤:

注意:尽管该算法被称为 remove unsafe,但在本规范中我们严格使用“unsafe”一词来表示在插入文档时会执行 JavaScript 的内容。换言之,此方法将移除可能导致 XSS 的机会。

  1. 断言键集合应当与 内置安全基线配置 的键集合相等, 即 «[ "removeElements", "removeAttributes" ] »。

  2. result 为 false。

  3. 对于每个 element内置安全基线配置[removeElements] 中:

    1. 调用 remove an element 将该 elementconfiguration 中移除。

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

  4. 对于每个 attribute内置安全基线配置[removeAttributes] 中:

    1. 调用 remove an attribute 将该 attributeconfiguration 中移除。

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

  5. 对于每个 列在 event handler content attributes 中的 attribute

    1. 调用 remove an attribute 将该 attributeconfiguration 中移除。

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

  6. 返回 result

3.3. 设置配置

设置配置,给定一个 字典 configuration, 一个 布尔值 allowCommentsAndDataAttributes,以及一个 Sanitizer sanitizer
  1. 使用 规范化configurationallowCommentsAndDataAttributes 进行处理。

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

  3. sanitizerconfiguration 设为 configuration

  4. 返回 true。

3.4. 规范化配置

Sanitizerconfiguration 以规范化形式存储,以便于后续多项处理步骤。

一个 elements 列表 {elements: ["div"]} 会被存储为 {elements: [{name: "div", namespace: "http://www.w3.org/1999/xhtml"}])。
规范化配置 SanitizerConfig configuration 并使用一个 布尔值 allowCommentsAndDataAttributes

注意:假设 configuration[WebIDL] 将 JavaScript 值转换为 SanitizerConfig 后的结果。

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

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

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

    1. elements 为 « [] »

    2. 遍历 configuration["elements"] 中的每个 element

      1. 规范化带属性的清洗器元素 element 的结果追加到 elements

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

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

    1. elements 为 « [] »

    2. 遍历 configuration["removeElements"] 中的每个 element

      1. 规范化清洗器元素 element 的结果追加到 elements

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

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

    1. elements 为 « [] »

    2. 遍历 configuration["replaceWithChildrenElements"] 中的每个 element

      1. 规范化清洗器元素 element 的结果追加到 elements

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

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

    1. attributes 为 « [] »

    2. 遍历 configuration["attributes"] 中的每个 attribute

      1. 规范化清洗器属性 attribute 的结果追加到 attributes

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

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

    1. attributes 为 « [] »

    2. 遍历 configuration["removeAttributes"] 中的每个 attribute

      1. 规范化清洗器属性 attribute 的结果追加到 attributes

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

  8. 如果 configuration["comments"] 不存在,则设置 configuration["comments"] 为 allowCommentsAndDataAttributes

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

要对一个 SanitizerElementWithAttributes element 执行 规范化
  1. result 为对 element 调用 canonicalize a sanitizer element 的结果。

  2. 如果 element 是一个 字典

    1. 对于每个 attribute 属于 element["attributes"]:

      1. 追加 规范化清理器属性 attribute 的结果到 result["attributes"]。

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

      1. 追加 规范化清理器属性 attribute 的结果到 result["removeAttributes"]。

  3. 返回 result

为了对一个 SanitizerElement element 执行 规范化,返回用 element 和默认命名空间为 HTML 命名空间 调用 canonicalize a sanitizer name 的结果。
为了对一个 SanitizerAttribute attribute 执行 规范化,返回用 attribute 和默认命名空间为 null 调用 canonicalize a sanitizer name 的结果。
为了规范化清理器名称 name,使用默认命名空间 defaultNamespace,请执行以下步骤:
  1. 断言name 要么是 DOMString ,要么是 字典

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

  3. 断言name字典,并且 name["name"] 存在

  4. namespacename["namespace"](如果其 存在),否则为 defaultNamespace

  5. 如果 namespace 是空字符串,则将其设为 null。

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

3.5. 辅助算法

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

如果存在 list 的某个 entry(一个 有序映射),且 item["name"] 等于 entry["name"] 且 item["namespace"] 等于 entry["namespace"],则认为该 Sanitizer 名称 list 包含item
要从一个作为 有序映射list移除 一个 item,请 移除 list 中所有满足 item["name"] 等于 entry["name"] 且 item["namespace"] 等于 entry["namespace"] 的 entry
要向一个 list(作为 已规范化name 的目标)中 添加 一个 name,其中 list 是一个 有序映射
  1. 如果 list 已经包含 name,则返回。

  2. name 追加list

有序集合的相等性是指成员相等,但不考虑顺序: 有序集合 AB 被认为是相等的,当且仅当 AB 的超集,且 BA 的超集
有序映射是由组成的元组序列。 有序映射的相等性是指这个元组序列作为有序集合时的相等性。 有序映射 AB 被认为是相等的,当且仅当 A条目组成的有序集合B条目组成的有序集合相等的。
一个列表 list 有重复项, 如果对于任意 item 属于 list, 存在多于一个 entry 属于 list, 且 item["name"] 等于 entry["name"], 并且 item["namespace"] 等于 entry["namespace"]。
列表 list的步骤如下:
  1. result 为 « [] »

  2. 对于每个 entry 属于 list添加 entryresult

  3. 返回 result

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

  2. set B 为 « [] »

  3. 对于每个 entry 属于 A追加 entry 经过 规范化后的结果到 set A

  4. 对于每个 entry 属于 B追加 entry 经过 规范化后的结果到 set B

  5. 返回 set A 和 set B 的交集

要判断布尔值 bool, 如果 bool 为 true,则返回 false,否则返回 true。
注释包含适用于算法中某个特定位置的说明性文本。

3.6. 内置项

有四个内置项:

内置安全默认配置 如下:

{
  "elements": [
    {
      "name": "html",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "head",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "title",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "body",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "article",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "section",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "nav",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "aside",
      "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": "hgroup",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "header",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "footer",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "address",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "p",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "hr",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "pre",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "blockquote",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "cite",
          "namespace": null
        }
      ]
    },
    {
      "name": "ol",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "reversed",
          "namespace": null
        },
        {
          "name": "start",
          "namespace": null
        },
        {
          "name": "type",
          "namespace": null
        }
      ]
    },
    {
      "name": "ul",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "menu",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "li",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "value",
          "namespace": null
        }
      ]
    },
    {
      "name": "dl",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "dt",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "dd",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "figure",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "figcaption",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "main",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "search",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "div",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "a",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "href",
          "namespace": null
        },
        {
          "name": "rel",
          "namespace": null
        },
        {
          "name": "hreflang",
          "namespace": null
        },
        {
          "name": "type",
          "namespace": null
        }
      ]
    },
    {
      "name": "em",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "strong",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "small",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "s",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "cite",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "q",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "dfn",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "abbr",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "ruby",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "rt",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "rp",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "data",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "value",
          "namespace": null
        }
      ]
    },
    {
      "name": "time",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "datetime",
          "namespace": null
        }
      ]
    },
    {
      "name": "code",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "var",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "samp",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "kbd",
      "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": "i",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "b",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "u",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "mark",
      "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": "span",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "br",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "wbr",
      "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": "del",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "cite",
          "namespace": null
        },
        {
          "name": "datetime",
          "namespace": null
        }
      ]
    },
    {
      "name": "table",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "caption",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "colgroup",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "span",
          "namespace": null
        }
      ]
    },
    {
      "name": "col",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "span",
          "namespace": null
        }
      ]
    },
    {
      "name": "tbody",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "thead",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "tfoot",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "tr",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": []
    },
    {
      "name": "td",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "colspan",
          "namespace": null
        },
        {
          "name": "rowspan",
          "namespace": null
        },
        {
          "name": "headers",
          "namespace": null
        }
      ]
    },
    {
      "name": "th",
      "namespace": "http://www.w3.org/1999/xhtml",
      "attributes": [
        {
          "name": "colspan",
          "namespace": null
        },
        {
          "name": "rowspan",
          "namespace": null
        },
        {
          "name": "headers",
          "namespace": null
        },
        {
          "name": "scope",
          "namespace": null
        },
        {
          "name": "abbr",
          "namespace": null
        }
      ]
    },
    {
      "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": "form",
          "namespace": null
        },
        {
          "name": "fence",
          "namespace": null
        },
        {
          "name": "separator",
          "namespace": null
        },
        {
          "name": "lspace",
          "namespace": null
        },
        {
          "name": "rspace",
          "namespace": null
        },
        {
          "name": "stretchy",
          "namespace": null
        },
        {
          "name": "symmetric",
          "namespace": null
        },
        {
          "name": "maxsize",
          "namespace": null
        },
        {
          "name": "minsize",
          "namespace": null
        },
        {
          "name": "largeop",
          "namespace": null
        },
        {
          "name": "movablelimits",
          "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": "width",
          "namespace": null
        },
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "depth",
          "namespace": null
        },
        {
          "name": "lspace",
          "namespace": null
        },
        {
          "name": "voffset",
          "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": "width",
          "namespace": null
        },
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "depth",
          "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": "svg",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "viewBox",
          "namespace": null
        },
        {
          "name": "preserveAspectRatio",
          "namespace": null
        },
        {
          "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": "defs",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "title",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "desc",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "metadata",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": []
    },
    {
      "name": "path",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "d",
          "namespace": null
        }
      ]
    },
    {
      "name": "rect",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        },
        {
          "name": "width",
          "namespace": null
        },
        {
          "name": "height",
          "namespace": null
        },
        {
          "name": "rx",
          "namespace": null
        },
        {
          "name": "ry",
          "namespace": null
        }
      ]
    },
    {
      "name": "circle",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "cx",
          "namespace": null
        },
        {
          "name": "cy",
          "namespace": null
        },
        {
          "name": "r",
          "namespace": null
        }
      ]
    },
    {
      "name": "ellipse",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "cx",
          "namespace": null
        },
        {
          "name": "cy",
          "namespace": null
        },
        {
          "name": "rx",
          "namespace": null
        },
        {
          "name": "ry",
          "namespace": null
        }
      ]
    },
    {
      "name": "line",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "x1",
          "namespace": null
        },
        {
          "name": "y1",
          "namespace": null
        },
        {
          "name": "x2",
          "namespace": null
        },
        {
          "name": "y2",
          "namespace": null
        }
      ]
    },
    {
      "name": "polyline",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "points",
          "namespace": null
        }
      ]
    },
    {
      "name": "polygon",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "pathLength",
          "namespace": null
        },
        {
          "name": "points",
          "namespace": null
        }
      ]
    },
    {
      "name": "text",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "lengthAdjust",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        },
        {
          "name": "dx",
          "namespace": null
        },
        {
          "name": "dy",
          "namespace": null
        },
        {
          "name": "rotate",
          "namespace": null
        },
        {
          "name": "textLength",
          "namespace": null
        }
      ]
    },
    {
      "name": "tspan",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "lengthAdjust",
          "namespace": null
        },
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        },
        {
          "name": "dx",
          "namespace": null
        },
        {
          "name": "dy",
          "namespace": null
        },
        {
          "name": "rotate",
          "namespace": null
        },
        {
          "name": "textLength",
          "namespace": null
        }
      ]
    },
    {
      "name": "textPath",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "lengthAdjust",
          "namespace": null
        },
        {
          "name": "textLength",
          "namespace": null
        },
        {
          "name": "path",
          "namespace": null
        },
        {
          "name": "startOffset",
          "namespace": null
        },
        {
          "name": "method",
          "namespace": null
        },
        {
          "name": "spacing",
          "namespace": null
        },
        {
          "name": "side",
          "namespace": null
        }
      ]
    },
    {
      "name": "foreignObject",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "x",
          "namespace": null
        },
        {
          "name": "y",
          "namespace": null
        },
        {
          "name": "width",
          "namespace": null
        },
        {
          "name": "height",
          "namespace": null
        }
      ]
    },
    {
      "name": "marker",
      "namespace": "http://www.w3.org/2000/svg",
      "attributes": [
        {
          "name": "viewBox",
          "namespace": null
        },
        {
          "name": "preserveAspectRatio",
          "namespace": null
        },
        {
          "name": "refX",
          "namespace": null
        },
        {
          "name": "refY",
          "namespace": null
        },
        {
          "name": "markerUnits",
          "namespace": null
        },
        {
          "name": "markerWidth",
          "namespace": null
        },
        {
          "name": "markerHeight",
          "namespace": null
        },
        {
          "name": "orient",
          "namespace": null
        }
      ]
    }
  ],
  "attributes": [
    {
      "name": "dir",
      "namespace": null
    },
    {
      "name": "lang",
      "namespace": null
    },
    {
      "name": "title",
      "namespace": null
    },
    {
      "name": "displaystyle",
      "namespace": null
    },
    {
      "name": "mathbackground",
      "namespace": null
    },
    {
      "name": "mathcolor",
      "namespace": null
    },
    {
      "name": "mathsize",
      "namespace": null
    },
    {
      "name": "scriptlevel",
      "namespace": null
    },
    {
      "name": "fill",
      "namespace": null
    },
    {
      "name": "transform",
      "namespace": null
    },
    {
      "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": "direction",
      "namespace": null
    },
    {
      "name": "display",
      "namespace": null
    },
    {
      "name": "dominant-baseline",
      "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": "letter-spacing",
      "namespace": null
    },
    {
      "name": "marker-end",
      "namespace": null
    },
    {
      "name": "marker-mid",
      "namespace": null
    },
    {
      "name": "marker-start",
      "namespace": null
    },
    {
      "name": "opacity",
      "namespace": null
    },
    {
      "name": "paint-order",
      "namespace": null
    },
    {
      "name": "pointer-events",
      "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": "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": "script"
    },
    {
      "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": "embed"
    },
    {
      "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": "script"
    },
    {
      "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": "embed"
    },
    {
      "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" → "iframe", "namespace" → HTML 命名空间 }, { "name" → "src", "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" → "animateMotion", "namespace" → SVG 命名空间 }, { "name" → "attributeName", "namespace" → null } ],
[ { "name" → "animateTransform", "namespace" → SVG 命名空间 }, { "name" → "attributeName", "namespace" → null } ],
[ { "name" → "set", "namespace" → SVG 命名空间 }, { "name" → "attributeName", "namespace" → null } ],

4. 安全注意事项

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

但需要注意,正确使用 Sanitizer API 并不能防护所有安全问题,以下各节将详细说明相关场景。

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

本节非规范性内容。

Sanitizer API 仅在 DOM 层运行,提供遍历和过滤现有 DocumentFragment 的能力。Sanitizer 不涉及服务端反射型或存储型 XSS 攻击。

4.2. DOM 属性覆盖攻击(DOM clobbering)

本节非规范性内容。

DOM clobbering 描述了一种攻击方式:恶意 HTML 通过 idname 属性命名元素,使 HTML 元素在 DOM 中的某些属性(如 children)被恶意内容覆盖,导致应用混乱。

Sanitizer API 默认状态下无法防护 DOM clobbering 攻击,但可以通过配置移除 idname 属性。

4.3. 利用脚本装置进行 XSS 攻击

本节非规范性内容。

脚本装置(Script gadgets)是一种攻击技术,攻击者利用流行 JavaScript 库的现有应用代码,使自己的代码得以执行。通常是通过注入看似无害的代码或 DOM 节点,这些内容会被某些框架解析并导致实际执行 JavaScript。

Sanitizer API 无法防止此类攻击,页面作者需自行决定是否允许未知元素,并且需明确配置允许哪些未知属性、元素,以及哪些被广泛用于模板和框架的标记,如 data-slot 属性、<slot><template> 元素等。我们认为这些限制并非穷尽,鼓励页面作者对第三方库的行为进行排查。

4.4. 变异型 XSS (mXSS)

本节非规范性内容。

变异型 XSS(mXSS)描述了基于解析器上下文不一致的攻击。例如,在没有正确上下文的情况下解析 HTML 片段,导致序列化为字符串后再次插入不同父元素时解析行为发生变化。攻击者可利用外来内容或标签嵌套错误来实现此类攻击。

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

如果开发者将净化后的节点树转为字符串(如通过 .innerHTML),再进行解析,则可能发生变异型 XSS。我们不推荐此做法。如确需处理或传递 HTML 字符串,则该字符串应被视为不可信,在插入 DOM 时应再次净化。换言之,净化并序列化后的 HTML 树不再被视为已净化。

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

5. 致谢

本项目参考并受到 [DOMPURIFY](cure53)、Internet Explorer 的 window.toStaticHTML() 以及 Ben Bucksch 的 [HTMLSanitizer] 的启发。 感谢 Anne van Kesteren、Krzysztof Kotowicz、Tom Schuster、Luke Warlow、Guillaume Weghsteen 和 Mike West 的宝贵反馈。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[DOM]
Anne van Kesteren. DOM 标准. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; 等. HTML 标准. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. Living Standard. 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 标准. Living Standard. URL: https://url.spec.whatwg.org/
[WebIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. Living Standard. 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 索引

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 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 SanitizerAttributeNamespace {
  required DOMString name;
  DOMString? _namespace = null;
};
typedef (DOMString or SanitizerAttributeNamespace) SanitizerAttribute;

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

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

  boolean comments;
  boolean dataAttributes;
};