HTML 정리자 API

커뮤니티 그룹 초안 보고서,

이 버전:
https://wicg.github.io/sanitizer-api/
이슈 추적:
GitHub
편집자:
Frederik Braun (Mozilla)
Mario Heiderich (Cure53)
Daniel Vogelheim (Google LLC)
Tom Schuster (Mozilla)

요약

이 문서는 개발자가 신뢰할 수 없는 HTML 입력을 받아 문서의 DOM에 안전하게 삽입할 수 있도록 정리(sanitize)할 수 있게 하는 일련의 API를 규정합니다.

이 문서의 상태

이 명세는 웹 플랫폼 인큐베이터 커뮤니티 그룹에서 발행했습니다. 이는 W3C 표준이 아니며 W3C 표준화 트랙에도 있지 않습니다. 다음에 유의하세요: W3C 커뮤니티 기여자 라이선스 계약(CLA)에 따라 제한적인 옵트아웃이 가능하며 기타 조건이 적용됩니다. 자세히 알아보기: W3C 커뮤니티 및 비즈니스 그룹.

1. 소개

이 절은 비규범적입니다.

웹 애플리케이션은 클라이언트 측에서 HTML 문자열을 다뤄야 하는 경우가 자주 있습니다. 클라이언트 측 템플릿 솔루션의 일부일 수도 있고, 사용자 생성 콘텐츠를 렌더링하는 과정의 일부일 수도 있습니다. 이를 안전한 방식으로 수행하는 것은 어렵습니다. 문자열을 단순히 이어 붙여 ElementinnerHTML 에 밀어 넣는 순진한 접근은 수많은 예상치 못한 방식으로 JavaScript 실행을 유발할 수 있어 매우 위험합니다.

[DOMPURIFY]와 같은 라이브러리는 삽입 전에 문자열을 신중히 파싱하고 정리(sanitize)하여 문제를 다루려 합니다. 즉, DOM을 구성한 다음 허용 목록을 통해 그 구성원을 필터링합니다. 하지만 이는 취약한 접근법으로 드러났습니다. 웹에 노출된 파싱 API는 실제로 문자열을 "실제" DOM으로 렌더링할 때의 브라우저 동작과 항상 합리적으로 매핑되지 않기 때문입니다. 게다가 라이브러리는 시간이 지나며 변화하는 브라우저 동작을 계속 따라가야 합니다. 한때 안전했던 것이 플랫폼 수준의 새로운 기능으로 인해 시한폭탄이 될 수 있습니다.

브라우저는 언제 코드를 실행할지에 대해 꽤 잘 알고 있습니다. 사용자 공간 라이브러리보다 더 나은 방법은, 브라우저가 임의 문자열로부터 HTML을 안전한 방식으로 렌더링하도록 가르치고, 브라우저 자체의 파서 구현이 변함에 따라 함께 유지·업데이트될 가능성이 훨씬 높은 방식으로 이를 수행하는 것입니다. 이 문서는 바로 그러한 목적을 달성하려는 API를 개략적으로 설명합니다.

1.1. 목표

1.2. API 요약

정리자 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신뢰된 타입 호환 문자열 얻기 알고리즘에 TrustedHTML, this관련 전역 객체, html, "Element setHTMLUnsafe", 그리고 "script"를 전달하여 호출한 결과로 한다.

  2. target을 다음으로 둡니다: thistemplate 요소인 경우 template contents, 그 외에는 this.

  3. Set and filter HTMLtarget, this, compliantHTML, options, false를 인수로 하여 수행합니다.

ElementsetHTML(html, options) 메서드 단계는 다음과 같습니다:
  1. target을 다음으로 둡니다: thistemplate인 경우 template contents, 그 외에는 this.

  2. Set and filter HTMLtarget, this, html, options, 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신뢰된 타입 호환 문자열 가져오기 알고리즘에 TrustedHTML, this관련 전역 객체, html, "ShadowRoot setHTMLUnsafe", 그리고 "script"를 전달하여 호출한 결과로 한다.

  2. Set and filter HTMLthis, thisshadow host(컨텍스트 요소로 사용), compliantHTML, options, false를 인수로 하여 수행합니다.

ShadowRootsetHTML(html, options) 메서드 단계는 다음과 같습니다:
  1. Set and filter HTMLthis(대상)와 this(컨텍스트 요소), html, options, 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. compliantHTMLget trusted type compliant string 알고리즘에 TrustedHTML, 현재 전역 객체, html, "Document parseHTMLUnsafe", 그리고 "script"를 전달하여 호출한 결과로 한다.

  2. document를 새 Document로 둡니다. 이때 content type은 "text/html"입니다.

    참고: document에는 탐색 컨텍스트가 없으므로 스크립팅이 비활성화됩니다.

  3. documentallow declarative shadow roots를 true로 설정합니다.

  4. Parse HTML from a stringdocumentcompliantHTML을 인수로 하여 수행합니다.

  5. sanitizerget a sanitizer instance from optionsoptions와 false로 호출한 결과로 둡니다.

  6. sanitizedocument에 대해 sanitizer 및 false로 호출합니다.

  7. document를 반환합니다.

parseHTML(html, options) 메서드 단계는 다음과 같습니다:
  1. document를 새 Document로 둡니다. 이때 content type은 "text/html"입니다.

    참고: document에는 탐색 컨텍스트가 없으므로 스크립팅이 비활성화됩니다.

  2. documentallow declarative shadow roots를 true로 설정합니다.

  3. Parse HTML from a stringdocumenthtml을 인수로 하여 수행합니다.

  4. sanitizerget a sanitizer instance from optionsoptions와 true로 호출한 결과로 둡니다.

  5. sanitizedocument에 대해 sanitizer 및 true로 호출합니다.

  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 구성이 있습니다.

constructor(configuration) 메서드 단계는 다음과 같습니다:
  1. configurationSanitizerPresets 문자열인 경우:

    1. 단정: configuration다음과 같다: default.

    2. configuration내장 안전 기본 구성으로 설정합니다.

  2. valid구성 설정configuration과 true로 this에 대해 호출한 반환값으로 둡니다.

  3. valid가 false이면 TypeError를 던집니다.

get() 메서드 단계는 다음과 같습니다:
참고: get() 메서드 외부에서는 Sanitizer의 요소와 속성의 순서가 관찰되지 않습니다. 이 메서드의 결과를 명시적으로 정렬함으로써, 내부적으로 정렬되지 않은 집합을 사용하는 등 구현체가 최적화할 수 있도록 합니다.
  1. configthisconfiguration으로 설정한다.

  2. 단언: config유효함을 보장한다.

  3. 만약 config["elements"] 존재하면:

    1. config["elements"]의 element에 대해:

      1. 만약 element["attributes"] 존재하면:

        1. element["attributes"]를 오름차순으로 정렬한 결과로 설정한다. attrAless than item attrB일 때.

      2. 만약 element["removeAttributes"] 존재하면:

        1. element["removeAttributes"]를 오름차순으로 정렬한 결과로 설정한다. attrAless than item attrB일 때.

    2. config["elements"]를 오름차순으로 정렬한 결과로 설정한다. elementAless than item elementB일 때.

  4. 그렇지 않으면:

    1. config["removeElements"]를 오름차순으로 정렬한 결과로 설정한다. elementAless than item elementB일 때.

  5. 만약 config["replaceWithChildrenElements"] 존재하면:

    1. config["replaceWithChildrenElements"]를 오름차순으로 정렬한 결과로 설정한다. elementAless than item elementB일 때.

  6. 만약 config["processingInstructions"] 존재하면:

    1. config["processingInstructions"]를 오름차순으로 정렬한 결과로 설정한다. piA["target"] 가 code unit less than piB["target"]일 때.

  7. 그렇지 않으면:

    1. config["removeProcessingInstructions"]를 오름차순으로 정렬한 결과로 설정한다. piA["target"] 가 code unit less than piB["target"]일 때.

  8. 만약 config["attributes"] 존재하면:

    1. config["attributes"]를 오름차순으로 정렬한 결과로 설정한다. attrAless than item attrB일 때.

  9. 그렇지 않으면:

    1. config["removeAttributes"]를 오름차순으로 정렬한 결과로 설정한다. attrAless than item attrB일 때.

  10. config를 반환한다.

allowElement(element) 메서드 단계는 다음과 같습니다:
참고: 이 알고리즘은 비교적 복잡합니다. 왜냐하면 요소 허용 목록이 요소별로 허용 또는 제거 속성 목록을 명시할 수 있기 때문입니다. 이로 인해 4가지 경우를 구분해야 합니다:
  • 전역 허용/제거 목록이 있는지 여부, 그리고

  • 해당 목록에 이미 element가 포함되어 있는지 여부.

  1. configurationthisconfiguration으로 설정한다.

  2. 단언: configuration유효함을 보장한다.

  3. element속성 포함 sanitizer 요소 정규화의 결과로 설정한다. element로서.

  4. 만약 configuration["elements"] 존재하면:

    1. modifiedremove element from configuration["replaceWithChildrenElements"]의 결과로 설정한다.

    2. 코멘트: 요소별 속성이 전역 속성과 중복되지 않도록 해야 한다.

    3. 만약 configuration["attributes"] 존재하면:

      1. 만약 element["attributes"] 존재하면:

        1. element["attributes"]를 중복 제거한 결과로 설정한다. element["attributes"]에서.

        2. element["attributes"]를 차집합 결과로 설정한다. element["attributes"] 와 configuration["attributes"].

        3. 만약 configuration["dataAttributes"]가 true이면:

          1. 제거 element["attributes"]의 모든 item커스텀 데이터 속성인 경우 제거한다.

      2. 만약 element["removeAttributes"] 존재하면:

        1. element["removeAttributes"]를 중복 제거한 결과로 설정한다.

        2. element["removeAttributes"]를 교집합 결과로 설정한다. element["removeAttributes"] 와 configuration["attributes"].

    4. 그렇지 않으면:

      1. 만약 element["attributes"] 존재하면:

        1. 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"]를 중복 제거한 결과로 설정한다.

        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["name"]이 동일하고 item["namespace"] 가 동일elementitem으로 설정한다.

    8. 만약 element동일하다면 current element와, modified를 반환한다.

    9. 제거 elementconfiguration["elements"]에서.

    10. 추가 elementconfiguration["elements"]에.

    11. true를 반환한다.

  5. 그렇지 않으면:

    1. 만약 element["attributes"] 존재하거나, element["removeAttributes"] 기본값 « »일 때 비어있지 않으면:

      1. 사용자 에이전트는 콘솔에 경고 표시를 할 수 있다.

      2. false를 반환한다.

    2. modifiedremove element from configuration["replaceWithChildrenElements"]의 결과로 설정한다.

    3. 만약 configuration["removeElements"] 포함하지 않는다면 element를:

      1. 코멘트: 전역 제거 목록에 element가 없는 경우.

      2. modified를 반환한다.

    4. 코멘트: 전역 제거 목록에 element가 이미 포함된 경우.

    5. 제거 elementconfiguration["removeElements"]에서.

    6. true를 반환한다.

removeElement(element) 메서드 단계는, 요소를 제거elementthisconfiguration으로 수행하는 것이다.
replaceElementWithChildren(element) 메서드 단계는 다음과 같다:
  1. configurationthis설정으로 둔다.

  2. 명시: configuration유효함.

  3. elementsanitizer 요소 정규화의 결과로 설정한다, element를 인자로 한다.

  4. 내장 비치환 요소 목록포함하고 있을 경우 element를:

    1. false를 반환한다.

  5. configuration["replaceWithChildrenElements"] 가 포함하고 있을 경우 element를:

    1. false를 반환한다.

  6. 제거 elementconfiguration["removeElements"] 에서.

  7. 제거 elementconfiguration["elements"] 목록에서.

  8. 추가 elementconfiguration["replaceWithChildrenElements"] 에.

  9. true를 반환한다.

allowProcessingInstruction(pi) 메서드 단계는 다음과 같습니다:
  1. configurationthisconfiguration으로 설정한다.

  2. 단언: configuration유효함을 보장한다.

  3. pisanitize 처리 명령문 정규화 결과로 설정한다.

  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. pisanitize 처리 명령문 정규화 결과로 설정한다.

  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. attributesanitizer 속성 정규화 결과로 설정한다.

  4. 만약 configuration["attributes"] 존재하면:

    1. 코멘트: 전역 허용 목록이 있다면, attribute를 추가해야 한다.

    2. 만약 configuration["dataAttributes"] 가 true이고 attribute커스텀 데이터 속성이라면, false를 반환한다.

    3. 만약 configuration["attributes"] 포함 attribute라면 false를 반환한다.

    4. 코멘트: 요소별 허용/제거 목록을 수정한다.

    5. 만약 configuration["elements"] 존재하면:

      1. element에 대해 configuration["elements"]:

        1. 만약 element["attributes"] 기본값 « » 포함 attribute라면:

          1. 제거 attributeelement["attributes"]에서.

        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커스텀 데이터 속성인 모든 항목을 제거한다.

    2. 만약 configuration["elements"] 존재하면:

      1. element에 대해 configuration["elements"]:

        1. 만약 element["attributes"] 존재하면:

          1. 제거 element["attributes"]의 attr커스텀 데이터 속성인 모든 항목을 제거한다.

  6. configuration["dataAttributes"]를 allow로 설정한다.

  7. true를 반환한다.

removeUnsafe() 메서드 단계는 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" sanitizer 메서드는 서로 다른 기본값을 가집니다.

모든 구성 딕셔너리가 유효한 것은 아닙니다. 유효한 구성은 중복(예: 동일한 요소를 두 번 허용하는 등)과 모순(예: 요소를 동시에 제거와 허용에 지정하는 등)을 피해야 합니다.

구성이 유효하려면 다음 조건들이 충족되어야 합니다:

elements 요소 허용 목록은 특정 요소에 대해 속성을 허용하거나 제거하도록 지정할 수도 있다. 이는 [HTML]의 구조를 반영하기 위한 것으로, 전역 속성과 특정 요소에 적용되는 로컬 속성을 모두 인지한다. 전역 및 로컬 속성은 혼용할 수 있으나, 어떤 속성이 한 목록에서는 허용되고 다른 목록에서는 금지되는 등 모호한 구성은 일반적으로 유효하지 않음을 유의하라.

전역 attributes 전역 removeAttributes
로컬 attributes 속성은 둘 중 어느 목록과 일치하면 허용된다. 중복은 허용되지 않는다. 속성은 로컬 허용 목록에 있는 경우에만 허용된다. 전역 제거 목록과 로컬 허용 목록 사이에는 중복 항목이 허용되지 않는다. 전역 제거 목록은 이 특정 요소에 대해서는 기능하지 않지만, 로컬 허용 목록이 없는 다른 요소에는 적용될 수 있음을 유의하라.
로컬 removeAttributes 속성은 전역 허용 목록에 있고 로컬 제거 목록에는 없는 경우 허용된다. 로컬 제거 목록은 전역 허용 목록의 부분집합이어야 한다. 속성은 어느 목록에도 없을 때 허용된다. 전역 제거 목록과 로컬 제거 목록 사이에는 중복 항목이 허용되지 않는다.

전역 목록과 요소별 목록 사이에서는 대체로 중복이 허용되지 않지만, 전역 허용 목록과 요소별 제거 목록의 조합에서는 후자가 전자에 대한 부분집합이어야 한다는 비대칭성이 있음을 유의하라. 위 표에서 중복에만 초점을 맞춘 발췌는 다음과 같다:

전역 attributes 전역 removeAttributes
로컬 attributes 중복은 허용되지 않는다. 중복은 허용되지 않는다.
로컬 removeAttributes 로컬 제거 목록은 전역 허용 목록의 부분집합이어야 한다. 중복은 허용되지 않는다.

dataAttributes 설정은 사용자 정의 데이터 속성을 허용한다. 위 규칙은 dataAttributes 를 허용 목록으로 간주하면 사용자 정의 데이터 속성에도 쉽게 확장된다:

전역 attributesdataAttributes 가 설정됨
로컬 attributes 모든 사용자 정의 데이터 속성이 허용된다. 사용자 정의 데이터 속성은 중복 항목이 되므로 어떤 허용 목록에도 기재되어서는 안 된다.
로컬 removeAttributes 사용자 정의 데이터 속성은 로컬 제거 목록에 기재되지 않은 한 허용된다. 중복 항목이 되므로 전역 허용 목록에는 사용자 정의 데이터 속성을 기재해서는 안 된다.

이 규칙들을 문장으로 정리하면:

정규화된 SanitizerConfig config유효한지(valid)를 판단하려면:

참고: 입력된 구성은 이전에 구성 정규화 단계를 거쳤다고 예상합니다. 여기서는 해당 알고리즘이 보장해야 하는 조건들만 단언(assert)합니다.

  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. 단언: 모든 SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace, SanitizerProcessingInstruction, 그리고 SanitizerAttributeNamespace 항목들은 모두 정규화되어 있어야 하며, 각각 요소 정규화, 처리 명령문 정규화, 속성 정규화를 거쳤다는 의미이다.

  8. 만약 config["elements"] 존재한다면:

    1. 만약 config["elements"] 중복이 있다면, false를 반환한다.

  9. 그렇지 않으면:

    1. 만약 config["removeElements"] 중복이 있다면, false를 반환한다.

  10. 만약 config["replaceWithChildrenElements"] 존재하며 중복이 있다면, false를 반환한다.

  11. 만약 config["processingInstructions"] 존재한다면:

    1. 만약 config["processingInstructions"] 중복 target이 있다면, false를 반환한다.

  12. 그렇지 않으면:

    1. 만약 config["removeProcessingInstructions"] 중복 target이 있다면, false를 반환한다.

  13. 만약 config["attributes"] 존재한다면:

    1. 만약 config["attributes"] 중복이 있다면, false를 반환한다.

  14. 그렇지 않으면:

    1. 만약 config["removeAttributes"] 중복이 있다면, false를 반환한다.

  15. 만약 config["replaceWithChildrenElements"] 존재한다면:

    1. element에 대해 config["replaceWithChildrenElements"]:

      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. element에 대해 config["elements"]:

        1. 만약 element["attributes"] 존재하며 element["attributes"] 중복이 있다면, false를 반환한다.

        2. 만약 element["removeAttributes"] 존재하고 element["removeAttributes"] 중복이 있다면, false를 반환한다.

        3. 만약 교집합config["attributes"] 와 element["attributes"] 기본값 « » 이 비어있지 않으면, false를 반환한다.

        4. 만약 element["removeAttributes"] 기본값 « » 이 subset이 아니라면, false를 반환한다.

        5. 만약 config["dataAttributes"] 가 true이고, element["attributes"] 에 커스텀 데이터 속성이 있다면, false를 반환한다.

    3. 만약 config["dataAttributes"] 가 true이고, config["attributes"] 에 커스텀 데이터 속성이 있다면, false를 반환한다.

  17. 그렇지 않으면:

    1. 만약 config["elements"] 존재한다면:

      1. element에 대해 config["elements"]:

        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를 반환한다.

참고: 구성 설정dictionary에서 할 경우, 약간의 정규화(normalization)가 일어납니다. 특히, 허용 목록(allow-list)과 제거 목록(remove-list)이 모두 없으면 이것을 빈 제거 목록으로 해석합니다. 따라서 {} 자체는 유효한 구성이 아니지만, 실제로 {removeElements:[], removeAttributes:[]}로 정규화되어 유효 구성이 됩니다. 이런 정규화 단계는, 없는 딕셔너리와 빈 딕셔너리를 동일하게 취급하기 위한 것이며, setHTMLUnsafe(txt)setHTMLUnsafe(txt, {sanitizer: {}}) 의 동작이 일치하게 해줍니다.

3. 알고리즘

HTML 설정 및 필터링을(를) 수행하려면, Element 또는 DocumentFragment target, Element contextElement, 문자열 html, 딕셔너리 options, 불리언 safe가 주어진다:
  1. safe이고 contextElementlocal name이 "script"이며, contextElementnamespaceHTML 네임스페이스 또는 SVG 네임스페이스라면, 반환한다.

  2. sanitizer옵션에서 sanitizer 인스턴스 얻기의 결과로 optionssafe를 넘겨 호출한 값으로 둔다.

  3. newChildrenHTML 프래그먼트 파싱 알고리즘의 결과로, contextElement, html, true를 넘겨 호출한 값으로 둔다.

  4. fragmentDocumentFragment의 새 인스턴스로 두고, node documentcontextElementnode document로 한다.

  5. newChildren의 각 node에 대해, fragment에 node 추가를 수행한다.

  6. sanitizefragment, sanitizer, safe로 호출한다.

  7. Replace all을 사용해 target 내에 fragment로 교체한다.

옵션에서 sanitizer 인스턴스 얻기 방법은 dictionary optionsboolean safe가 주어졌을 때:

참고: 이 알고리즘은 SetHTMLOptionsSetHTMLUnsafeOptions 모두에 적용됩니다. 둘은 기본값만 다릅니다.

  1. sanitizerSpec을 "default"로 설정한다.

  2. 만약 options["sanitizer"] 존재한다면:

    1. sanitizerSpecoptions["sanitizer"] 로 설정한다.

  3. 단언: sanitizerSpecSanitizer 인스턴스, 문자열(string)로서 SanitizerPresets 멤버이거나, dictionary 중 하나여야 한다.

  4. 만약 sanitizerSpec문자열(string)이라면:

    1. 단언: sanitizerSpec"is" "default"

    2. sanitizerSpec내장 안전 기본 구성으로 설정한다.

  5. 단언: sanitizerSpecSanitizer 인스턴스 또는 dictionary이어야 한다.

  6. 만약 sanitizerSpecdictionary라면:

    1. sanitizer를 새로운 Sanitizer 인스턴스로 생성한다.

    2. setConfigurationResult구성 설정 의 결과(인자: sanitizerSpec, not safe)로 설정한다(sanitizer에서 실행).

    3. 만약 setConfigurationResult가 false면, throw를 발생시킨다. TypeError.

    4. sanitizerSpecsanitizer로 설정한다.

  7. 단언: sanitizerSpecSanitizer 인스턴스여야 한다.

  8. sanitizerSpec을 반환한다.

3.1. 정리(Sanitize)

메인 sanitize 동작은 ParentNode nodeSanitizer sanitizer, 그리고 boolean safe가 있을 때 다음 단계를 수행한다:
  1. configurationsanitizerconfiguration 값으로 설정한다.

  2. 단언: configuration유효하다.

  3. 만약 safe가 true면 configurationremove unsafeconfiguration에 호출한 결과로 재설정한다.

  4. sanitize corenode, configuration, 그리고 handleJavascriptNavigationUrlssafe로 설정하여 호출한다.

sanitize core 동작은 ParentNode node, SanitizerConfig configuration, 그리고 boolean handleJavascriptNavigationUrlsnode부터 DOM 트리를 재귀적으로 순회한다. 단계는 다음과 같다:
  1. nodechildrenchild에 대해:

    1. 단언: childText, Comment, Element, ProcessingInstruction 또는 DocumentType을 구현한다고 단언.

      참고: 현재 이 알고리즘은 HTML 파서의 출력에만 호출된다(이 단언이 성립함). DocumentTypeparseHTMLparseHTMLUnsafe 에만 나타난다. 이후 다른 컨텍스트에서 사용하려면 이 가정 재검토 필요.

    2. 만약 childDocumentType 구현체라면, 계속.

    3. 만약 childText 구현체라면, 계속.

    4. 만약 childComment 구현체라면:

      1. configuration["comments"] 가 true가 아니면, child 제거.

    5. 만약 childProcessingInstruction 구현체라면:

      1. piTargetchildtarget으로 설정.

      2. 만약 configuration["processingInstructions"] 존재한다면:

        1. configuration["processingInstructions"] 가 piTarget을 포함하지 않으면:

          1. child 제거

      3. 그 외의 경우:

        1. configuration["removeProcessingInstructions"] 가 piTarget을 포함하면:

          1. child 제거

    6. 위 모두 해당하지 않으면(즉, Element):

      1. elementNamechild로컬 이름네임스페이스를 가진 SanitizerElementNamespace라고 하자.

      2. configuration["replaceWithChildrenElements"]가 존재하고, configuration["replaceWithChildrenElements"]가 elementName포함하면:

        1. Assert: nodeDocument구현하지 않는다.

        2. child에 대해 configurationhandleJavascriptNavigationUrls와 함께 sanitize core를 호출한다.

        3. fragment를, 노드 문서node노드 문서인 새 DocumentFragment라고 하자.

        4. child자식 각각의 innerChild에 대해, innerChildfragmentappend한다.

        5. node 내에서 childfragment교체한다.

          NOTE: 여기서는 Replace가 throw되어서는 안 된다. 알고리즘의 성공적인 실행을 위한 구조적 전제조건이 충족되어 있어야 하기 때문이다.

        6. 계속한다.

      3. configuration["elements"]가 존재하면:

        1. configuration["elements"]가 elementName포함하지 않으면:

          1. child제거한다.

          2. 계속한다.

      4. 그렇지 않으면:

        1. configuration["removeElements"]가 elementName포함하면:

          1. child제거한다.

          2. 계속한다.

      5. elementName이 «[ "name" → "template", "namespace" → HTML 네임스페이스 ]»와 같으면, childtemplate contentsconfigurationhandleJavascriptNavigationUrls와 함께 sanitize core를 호출한다.

      6. childshadow host이면, childshadow rootconfigurationhandleJavascriptNavigationUrls와 함께 sanitize core를 호출한다.

      7. elementWithLocalAttributes를 « [] »라고 하자.

      8. configuration["elements"]가 존재하고, configuration["elements"]가 elementName포함하면:

        1. elementWithLocalAttributesconfiguration["elements"][elementName]로 설정한다.

      9. child속성 목록 안의 각 attribute에 대해:

        1. attrNameattribute로컬 이름네임스페이스를 가진 SanitizerAttributeNamespace라고 하자.

        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]»가 내장 navigating URL 속성 목록의 항목과 일치하고, attributejavascript: URL을 포함하면, attribute제거한다.

          2. child네임스페이스MathML Namespace이고, attr로컬 이름이 "href"이며, attr네임스페이스null이거나 XLink 네임스페이스이고, attrjavascript: URL을 포함하면, attribute제거한다.

          3. 내장 animating URL 속성 목록이 «[elementName, attrName]»를 포함하고, attr이 "href" 또는 "xlink:href"이면, attribute제거한다.

      10. child에 대해 configurationhandleJavascriptNavigationUrls와 함께 sanitize core를 호출한다.

참고: 현재 브라우저들은 javascript: URL을 네비게이션 상황에서만 지원한다. 네비게이션 자체는 XSS 위협이 아니기 때문에 navigation to javascript: URL일 땐 처리하지만, 일반적인 네비게이션은 모두 다루지 않는다.

선언적 네비게이션은 다음 범주로 구분된다:

  1. Anchor 요소. (HTML/SVG 네임스페이스의 <a> )

  2. 폼 요소 중 폼 action 결과로 네비게이션이 발생하는 경우.

  3. [MathML]임의의 요소를 앵커로 사용할 수 있도록 허용한다.

  4. [SVG11] 애니메이션.

첫 두 경우는 built-in navigating URL attributes list 로 처리된다.

MathML의 경우는 별도 규칙으로 처리되는데, 이 명세에는 "네임스페이스별 전역" 규칙을 처리할 포멀리즘이 없기 때문이다.

SVG 애니메이션은 built-in animating URL attributes list로 처리한다. 하지만 SVG 애니메이션의 실제 해석은 animation target에 따라 달라지고, sanitization 중에는 실제 target을 알 수 없으므로, sanitize 알고리즘에서는 href 속성의 animation을 모두 차단한다.

attributejavascript: URL을 포함하는지 판단하는 방법:
  1. urlattributevalue기본 URL 파서를 적용한 결과로 설정한다.

  2. urlfailure라면 false를 반환한다.

  3. urlscheme"javascript" 인지 반환한다.

3.2. 구성 수정

구성 수정 메서드는 Sanitizer의 메서드로, 해당 인스턴스의 구성을 수정합니다. 이 메서드들은 항상 유효성 기준을 유지합니다. 또한, 구성에 수정이 발생했는지 여부를 나타내는 boolean을 반환합니다.

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>`는 제거됨.
SanitizerElement elementSanitizerConfig configuration에서 제거하려면:
참고: 이 메서드는 아래 4가지 경우를 구분하여 처리해야 합니다:
  • 전역 허용/제거 목록 중 어떤 것을 가지고 있는지,

  • 이미 element가 해당 목록에 들어있는지 여부.

  1. 단언: configuration유효함.

  2. elementsanitizer 요소 정규화의 결과로 재설정한다.

  3. modified구성에서 element 제거 결과로 설정한다. configuration["replaceWithChildrenElements"].

  4. 만약 configuration["elements"] 존재하면:

    1. configuration["elements"] 가 element 포함하면:

      1. 코멘트: 전역 허용 리스트에 element가 포함됨.

      2. element를 구성에서 제거한다. configuration["elements"]에서.

      3. true를 반환한다.

    2. 코멘트: 전역 허용 리스트에 element가 없음.

    3. modified를 반환한다.

  5. 그 외의 경우:

    1. configuration["removeElements"] 가 element 포함하면:

      1. 코멘트: 전역 제거 목록에 이미 포함되어 있음.

      2. modified를 반환한다.

    2. 코멘트: 전역 제거 목록에 element가 없음.

    3. element를 구성의 제거 목록에 추가 configuration["removeElements"]에.

    4. true를 반환한다.

SanitizerAttributeattributeSanitizerConfig configuration에서 제거하려면:

참고: 이 메서드는 전역 허용/제거 목록을 구분합니다. attribute를 전역 제거 목록에 추가하면, 유효성 유지 기준을 위해 요소별 허용/제거 목록도 추가 조정이 필요할 수 있습니다. 전역 허용 목록에서 attribute를 제거할 때도, 로컬 제거 목록에서 해당 속성 제거가 필요할 수 있습니다.

  1. 단언: configuration유효함.

  2. attributesanitizer 속성 정규화 결과로 재설정한다.

  3. 만약 configuration["attributes"] 존재한다면:

    1. 코멘트: 전역 허용 목록일 경우 attribute 제거 필요.

    2. modified구성에서 attribute 제거의 결과로 설정; configuration["attributes"]에서.

    3. 코멘트: 요소별 허용/제거 목록 추가 조정

    4. 만약 configuration["elements"] 존재한다면:

      1. element에 대해 configuration["elements"]:

        1. element["attributes"] 기본값 « » 에 attribute 포함시:

          1. modified를 true로 설정

          2. attribute 제거; element["attributes"]에서.

        2. element["removeAttributes"] 기본값 « »에 attribute 포함시:

          1. 단언: modified는 true여야 함.

          2. attribute 제거; element["removeAttributes"]에서.

    5. modified를 반환한다.

  4. 그 외의 경우:

    1. 코멘트: 전역 제거 목록이면 attribute를 추가해야 함.

    2. configuration["removeAttributes"] 가 attribute 포함시 false 반환.

    3. 코멘트: 요소별 허용 및 제거 목록 추가 조정.

    4. 만약 configuration["elements"] 존재한다면:

      1. element에 대해 configuration["elements"]:

        1. element["attributes"] 기본값 « »에 attribute 포함시:

          1. attribute 제거; element["attributes"]에서.

        2. element["removeAttributes"] 기본값 « »에 attribute 포함시:

          1. attribute 제거; element["removeAttributes"]에서.

    5. attribute를 구성의 removeAttributes에 append configuration["removeAttributes"]

    6. true를 반환한다.

SanitizerConfig configuration에서 remove unsafe를 수행하려면 다음과 같이 한다:

참고: 이 알고리즘이 remove unsafe라고 불리지만, 이 명세에서 "unsafe"란 용어는 문서에 삽입시 JavaScript가 실행될 수 있는 콘텐츠만 의미합니다. 즉, 이 메서드는 XSS 위험을 제거하는 역할을 합니다.

  1. 단언: 키 집합(내장 안전 기준 구성) 는 다음과 같다 « [ "removeElements", "removeAttributes" ] ».

  2. 단언: configuration유효함.

  3. result를 false로 설정한다.

  4. element에 대해 내장 안전 베이스라인 구성["removeElements"]:

    1. remove an elementelement에 대해 호출한다. configuration에서.

    2. 호출 결과가 true라면 result를 true로 설정.

  5. attribute에 대해 내장 안전 베이스라인 구성["removeAttributes"]:

    1. remove an attributeattribute에 대해 호출한다. configuration에서.

    2. 호출 결과가 true라면 result를 true로 설정.

  6. attribute에 대해 이벤트 핸들러 content attributes 목록에서:

    1. remove an attributeattribute에 대해 호출한다. configuration에서.

    2. 호출 결과가 true라면 result를 true로 설정.

  7. result를 반환한다.

3.3. 구성 설정

구성을 설정하기 알고리즘은 다음 인자를 받는다: dictionary configuration, boolean allowCommentsPIsAndDataAttributes, 그리고 Sanitizer sanitizer:
  1. configurationallowCommentsPIsAndDataAttributes정규화(Canonicalize)를 수행한다.

  2. configuration유효(valid)하지 않으면, false를 반환한다.

  3. sanitizerconfigurationconfiguration으로 설정한다.

  4. true를 반환한다.

3.4. 구성 정규화

Sanitizer구성(configuration)을 정규화된 형태로 저장하며, 이는 여러 처리 단계를 더 쉽게 만들기 때문이다.

An elements 목록 {elements: ["div"]}{elements: [{name: "div", namespace: "http://www.w3.org/1999/xhtml"}]로 저장된다).
SanitizerConfig configuration불리언 allowCommentsPIsAndDataAttributes와 함께 설정을 정규화하려면:

Note: 우리는 configuration이 JavaScript 값을 SanitizerConfig로 변환한 [WebIDL]의 결과라고 가정한다.

  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에 대해 속성을 가진 sanitizer 요소를 정규화한 결과를 elementsappend한다.

    3. configuration["elements"]를 elements로 설정한다.

  5. configuration["removeElements"]가 존재하면:

    1. elements를 « »라고 하자.

    2. configuration["removeElements"]의 각 element에 대해 반복한다:

      1. element에 대해 sanitizer 요소를 정규화한 결과를 elementsappend한다.

    3. configuration["removeElements"]를 elements로 설정한다.

  6. configuration["replaceWithChildrenElements"]가 존재하면:

    1. elements를 « »라고 하자.

    2. configuration["replaceWithChildrenElements"]의 각 element에 대해 반복한다:

      1. element에 대해 sanitizer 요소를 정규화한 결과를 elementsappend한다.

    3. configuration["replaceWithChildrenElements"]를 elements로 설정한다.

  7. configuration["processingInstructions"]가 존재하면:

    1. processingInstructions를 « »라고 하자.

    2. configuration["processingInstructions"]의 각 pi에 대해 반복한다:

      1. pi에 대해 sanitizer 처리 명령을 정규화한 결과를 processingInstructionsappend한다.

    3. configuration["processingInstructions"]를 processingInstructions로 설정한다.

  8. configuration["removeProcessingInstructions"]가 존재하면:

    1. processingInstructions를 « »라고 하자.

    2. configuration["removeProcessingInstructions"]의 각 pi에 대해 반복한다:

      1. pi에 대해 sanitizer 처리 명령을 정규화한 결과를 processingInstructionsappend한다.

    3. configuration["removeProcessingInstructions"]를 processingInstructions로 설정한다.

  9. configuration["attributes"]가 존재하면:

    1. attributes를 « »라고 하자.

    2. configuration["attributes"]의 각 attribute에 대해 반복한다:

      1. attribute에 대해 sanitizer 속성을 정규화한 결과를 attributesappend한다.

    3. configuration["attributes"]를 attributes로 설정한다.

  10. configuration["removeAttributes"]가 존재하면:

    1. attributes를 « »라고 하자.

    2. configuration["removeAttributes"]의 각 attribute에 대해 반복한다:

      1. attribute에 대해 sanitizer 속성을 정규화한 결과를 attributesappend한다.

    3. configuration["removeAttributes"]를 attributes로 설정한다.

  11. configuration["comments"]가 존재하지 않으면, configuration["comments"]를 allowCommentsPIsAndDataAttributes설정한다.

  12. configuration["attributes"]가 존재하고 configuration["dataAttributes"]가 존재하지 않으면, configuration["dataAttributes"]를 allowCommentsPIsAndDataAttributes설정한다.

SanitizerElementWithAttributes element속성을 가진 sanitizer 요소를 정규화하려면:
  1. resultelement와 함께 sanitizer 요소를 정규화한 결과라고 하자.

  2. elementdictionary이면:

    1. element["attributes"]가 존재하면:

      1. attributes를 « »라고 하자.

      2. element["attributes"]의 각 attribute에 대해 반복한다:

        1. attribute와 함께 sanitizer 속성을 정규화한 결과를 attributesappend한다.

      3. result["attributes"]를 attributes설정한다.

    2. element["removeAttributes"]가 존재하면:

      1. attributes를 « »라고 하자.

      2. element["removeAttributes"]의 각 attribute에 대해 반복한다:

        1. attribute와 함께 sanitizer 속성을 정규화한 결과를 attributesappend한다.

      3. result["removeAttributes"]를 attributes설정한다.

  3. result["attributes"]와 result["removeAttributes"]가 모두 존재하지 않으면:

    1. result["removeAttributes"]를 « »로 설정한다.

  4. result를 반환한다.

sanitizer 요소를 정규화하려면 a SanitizerElement element, sanitizer name 정규화elementHTML namespace를 기본 namespace로 하여 수행한 결과를 반환한다.
pisanitizer processing instruction을 정규화하려면, 다음 단계를 수행한다:
  1. 단언: piDOMString 또는 dictionary 중 하나이다.

  2. 만약 piDOMString라면, «[ "target" → pi ]»를 반환한다.

  3. 단언: pidictionary이며 pi["target"]가 존재한다.

  4. «[ "target" → pi["target"] ]»를 반환한다.

sanitizer attribute를 정규화하려면 a SanitizerAttribute attribute, null을 기본 namespace로 하여 sanitizer name 정규화를 수행한 결과를 반환한다.
name을 기본 namespace defaultNamespace와 함께 sanitizer name을 정규화하려면, 다음 단계를 수행한다:
  1. 단언: nameDOMString 또는 dictionary 중 하나이다.

  2. 만약 nameDOMString라면, «[ "name" → name, "namespace" → defaultNamespace]»를 반환한다.

  3. 단언: namedictionary이며, name["name"]과 name["namespace"]가 모두 존재한다.

  4. name["namespace"]가 빈 문자열이면, 이를 null로 설정한다.

  5. «[
    "name" → name["name"],
    "namespace" → name["namespace"]
    ]»를 반환한다.

3.5. 지원 알고리즘

이 명세에서 사용하는 정규화된 elementattribute name 목록의 경우, 목록 멤버십은 "name"과 "namespace" 항목이 모두 일치하는지에 기반한다:

Sanitizer 이름 listitem을 포함한다(contains)고 판단하는 기준은, list 내에 ordered map 타입의 entry가 존재하고, item["name"]가 entry["name"]와 동일하며(equals), item["namespace"]가 entry["namespace"]와 동일한 경우이다.
Sanitizer target listtarget을 포함함(contains a target) target이란, list 내에 ordered map 타입의 entry가 있고, targetentry["target"]과 동일할 때를 의미한다.
remove: itemlist list에서 제거하려면:
  1. removed를 false로 설정한다.

  2. listentry에 대해:

    1. 만약 item["name"]이 entry["name"]과 동일, 그리고 item["namespace"]가 entry["namespace"]와 동일하다면:

      1. list에서 entry를 제거한다.

      2. removed를 true로 설정.

  3. removed를 반환한다.

add: 이미 정규화(canonicalized)namelist라는 ordered map에 추가하려면:
  1. 만약 listname을 포함한다면, 아무 작업도 하지 않고 return.

  2. name을 list에 append한다.

itemAitemB보다 less than item인지 판단하는 법:
  1. 만약 itemA["namespace"]가 null이라면:

    1. itemB["namespace"]가 null이 아니면 true 반환.

  2. 그 외:

    1. itemB["namespace"]가 null이면 false 반환.

    2. itemA["namespace"]가 code unit less than itemB["namespace"]이면 true 반환.

    3. itemA["namespace"]가 itemB["namespace"]와 다르다면 false 반환.

  3. 마지막으로 itemA["name"]이 code unit less than itemB["name"]이면 true, 아니면 false 반환.

ordered set의 동등성(equality)은 집합의 멤버가 동일한지(순서 무관)로 판정한다. Ordered set AB는, equal(동등)하다 판정되는 경우란 AB의 superset이고, BA의 superset인 경우이다.
순서가 있는 맵(ordered map)키(key)값(value)튜플(tuple) 시퀀스이다. 순서가 있는 맵의 동등성(equality)은 이 튜플 시퀀스를 순서가 있는 집합(ordered set)으로 간주해서 비교한다. 순서가 있는 맵 AB동등(equals)하다고 하는 기준은, A엔트리(entries)로 이루어진 순서가 있는 집합과, B엔트리로 이루어진 순서가 있는 집합동등(equal)하면 된다.
list list중복을 가진다는 것은, listitem 어떤 것에 대해, item["name"]이 entry["name"]이고 item["namespace"]가 entry["namespace"]인 entrylist 안에 둘 이상 있는 경우를 말한다.
list list중복 target을 가진다는 것은, listitem 어떤 것에 대해, item["target"]이 entry["target"]인 entrylist 안에 둘 이상 있는 경우를 말한다.
list list에서 중복을 제거하려면,
  1. result를 « »라고 하자.

  2. list의 각 entry에 대해 반복하여, entryresult추가한다.

  3. result를 반환한다.

SanitizerElement를 포함하는 두 list ABintersectionset intersection과 같지만, set 항목이 미리 정규화된다는 점이 다르다:
  1. set A를 « [] »라고 하자.

  2. set B를 « [] »라고 하자.

  3. A의 각 entry에 대해 반복하여, entry에 대해 sanitizer 이름을 정규화한 결과를 set Aappend한다.

  4. B의 각 entry에 대해 반복하여, entry에 대해 sanitizer 이름을 정규화한 결과를 set Bappend한다.

  5. set Aset Bintersection을 반환한다.

boolean boolnot을 결정하려면, bool이 true이면 false를 반환하고, 그렇지 않으면 true를 반환한다.
Comment는 알고리즘 안의 특정 지점에 적용되는 설명 텍스트를 포함한다.

3.6. 내장값

다섯 가지 내장(builtin)이 있습니다:

내장 안전 기본 구성은 다음과 같다:

{
  "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"
  ]
}
"javascript:" 탐색이 "unsafe"로 간주되는 내장 navigating URL 속성 목록은 다음과 같다:

«[
[ { "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

이 절은 규범(normative)이 아닙니다.

Sanitizer API는 DOM 내에서만 동작하며, 기존 DocumentFragment를 순회 및 필터링하는 기능을 추가합니다. Sanitizer는 서버 측 반사(reflected) 또는 저장(stored) XSS를 다루지 않습니다.

4.2. DOM 클로버링

이 절은 규범(normative)이 아닙니다.

DOM 클로버링은 악의적인 HTML이 idname 속성을 통해 요소 이름을 지정함으로써, HTML 요소의 children과 같은 프로퍼티가 악성 콘텐츠에 의해 가려지는 공격을 의미합니다.

Sanitizer API는 기본 상태에서는 DOM 클로버링 공격을 막지 못하지만, idname 속성을 제거하도록 구성할 수 있습니다.

4.3. 스크립트 gadget을 이용한 XSS

이 절은 규범(normative)이 아닙니다.

스크립트 gadget은 공격자가 인기있는 JavaScript 라이브러리의 기존 애플리케이션 코드를 활용하여 자신의 코드를 실행하게 만드는 기법입니다. 종종 무해해 보이는 코드나 inert DOM 노드를 삽입해 프레임워크가 이를 파싱 및 해석하여 JavaScript 실행에 이용하게 하는 방식입니다.

Sanitizer API만으로는 이러한 공격을 막을 수 없으며, 페이지 저자는 일반적으로 알 수 없는 요소를 명시적으로 허용해야 하고, data-slot 속성, <slot>, <template>과 같은 템플릿/프레임워크 전용 마크업에 대해서도 명시적으로 허용/제거 구성을 해야 합니다. 이러한 제한은 완전하지 않으므로, 페이지 저자에게는 사용하는 서드파티 라이브러리의 동작을 면밀히 검토할 것을 권장합니다.

4.4. Mutated XSS

이 절은 규범(normative)이 아닙니다.

Mutated XSS(mXSS)는 HTML 조각을 올바른 컨텍스트 없이 파싱할 때 발생하는 파서 컨텍스트 불일치에 기반한 공격입니다. 특히, 파싱된 HTML 프래그먼트를 문자열로 직렬화한 뒤, 다른 부모 요소에 삽입하여 재파싱하면 파싱 및 해석 결과가 동일하다고 보장할 수 없습니다. 예를 들어 foreign content나 잘못 중첩된 태그의 파싱 동작 변화에 의존하여 공격이 이루어질 수 있습니다.

Sanitizer API는 문자열을 노드 트리로 변환하는 함수만 제공합니다. 컨텍스트는 모든 sanitizer 함수에서 암묵적으로 제공됩니다: Element.setHTML()은 현재 요소를, Document.parseHTML()은 새 문서를 사용합니다. 따라서 Sanitizer API는 mutated XSS에 직접적으로 영향을 받지 않습니다.

개발자가 정리된(sanitized) 노드 트리를 .innerHTML 등으로 문자열로 변환한 뒤, 이를 다시 파싱한다면 mutated 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 Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. 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 Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WebIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. 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. Mathematical Markup Language (MathML™) 1.01 Specification. 7 March 2023. REC. URL: https://www.w3.org/TR/REC-MathML/
[MXSS]
mXSS Attacks: Attacking well-secured Web-Applications by using innerHTML Mutations. URL: https://cure53.de/fp170.pdf
[SafeMathML]
MathML Safe List. URL: https://w3c.github.io/mathml-docs/mathml-safe-list
[SVG11]
Erik Dahlström; et al. Scalable Vector Graphics (SVG) 1.1 (Second Edition). 16 August 2011. 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;
};