dompurify 2.1.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,11 +6,11 @@
6
6
 
7
7
  DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
8
8
 
9
- It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version 2.1.0.
9
+ It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version 2.2.2.
10
10
 
11
- DOMPurify is written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Edge, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on MSIE6 or other legacy browsers. It either uses [a fall-back](#what-about-older-browsers-like-msie8) or simply does nothing.
11
+ DOMPurify is written in JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Internet Explorer (10+), Edge, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on MSIE6 or other legacy browsers. It either uses [a fall-back](#what-about-older-browsers-like-msie8) or simply does nothing.
12
12
 
13
- Our automated tests cover [15 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L5) right now, more to come. We also cover Node.js v12.0.0 and v13.0.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). Older Node.js versions are known to work as well.
13
+ Our automated tests cover [15 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L5) right now, more to come. We also cover Node.js v12, v13, v14.0.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). Older Node.js versions are known to work as well.
14
14
 
15
15
  DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model). Please, read it. Like, really.
16
16
 
@@ -46,8 +46,6 @@ The resulting HTML can be written into a DOM element using `innerHTML` or the DO
46
46
 
47
47
  Well, please note, if you _first_ sanitize HTML and then modify it _afterwards_, you might easily **void the effects of sanitization**. If you feed the sanitized markup to another library _after_ sanitization, please be certain that the library doesn't mess around with the HTML on its own.
48
48
 
49
- jQuery does exactly that and that is why we have this flag mentioned above.
50
-
51
49
  ### Okay, makes sense, let's move on
52
50
 
53
51
  After sanitizing your markup, you can also have a look at the property `DOMPurify.removed` and find out, what elements and attributes were thrown out. Please **do not use** this property for making any security critical decisions. This is just a little helper for curious minds.
@@ -107,7 +105,7 @@ How does purified markup look like? Well, [the demo](https://cure53.de/purify) s
107
105
  ```js
108
106
  DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // becomes <img src="x">
109
107
  DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
110
- DOMPurify.sanitize('<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>'); // becomes <p>abcdef</p>
108
+ DOMPurify.sanitize('<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>'); // becomes <p>abc</p>
111
109
  DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math>
112
110
  DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
113
111
  DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>
@@ -203,6 +201,9 @@ var clean = DOMPurify.sanitize(dirty, {ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|
203
201
 
204
202
  /**
205
203
  * Influence the return-type
204
+ *
205
+ * Careful, this setting has foot-gun potential! If you set RETURN_DOM or RETURN_DOM_FRAGMENT to true, don't set RETURN_DOM_IMPORT to false!
206
+ * By default, our settings are secure - we believe - but returning a DOM *and* manually setting RETURN_DOM_IMPORT to false will give you XSS in some situations.
206
207
  */
207
208
  // return a DOM HTMLBodyElement instead of an HTML string (default is false)
208
209
  var clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});
@@ -213,7 +214,7 @@ var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true});
213
214
  // return a DOM DocumentFragment instead of an HTML string (default is false)
214
215
  // also import it into the current document (default is false).
215
216
  // RETURN_DOM_IMPORT must be set if you would like to append
216
- // the returned node to the current document
217
+ // the returned node to the current document (default is true)
217
218
  var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true, RETURN_DOM_IMPORT: true});
218
219
  document.body.appendChild(clean);
219
220
 
@@ -326,7 +327,7 @@ Feature releases will not be announced to this list.
326
327
 
327
328
  Many people helped and help DOMPurify become what it is and need to be acknowledged here!
328
329
 
329
- [oreoshake 💸](https://github.com/oreoshake), [dcramer 💸](https://github.com/dcramer),[tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@filedescriptor](https://twitter.com/filedescriptor), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro) and especially [@masatokinugawa](https://twitter.com/masatokinugawa)
330
+ [oreoshake 💸](https://github.com/oreoshake), [dcramer 💸](https://github.com/dcramer),[tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro) and especially [@masatokinugawa](https://twitter.com/masatokinugawa)
330
331
 
331
332
  ## Testing powered by
332
333
  <a target="_blank" href="https://www.browserstack.com/"><img width="200" src="https://www.browserstack.com/images/layout/browserstack-logo-600x315.png"></a><br>
@@ -40,10 +40,8 @@ if (!construct) {
40
40
  }
41
41
 
42
42
  var arrayForEach = unapply(Array.prototype.forEach);
43
- var arrayIndexOf = unapply(Array.prototype.indexOf);
44
43
  var arrayPop = unapply(Array.prototype.pop);
45
44
  var arrayPush = unapply(Array.prototype.push);
46
- var arraySlice = unapply(Array.prototype.slice);
47
45
 
48
46
  var stringToLowerCase = unapply(String.prototype.toLowerCase);
49
47
  var stringMatch = unapply(String.prototype.match);
@@ -119,7 +117,7 @@ function clone(object) {
119
117
  return newObject;
120
118
  }
121
119
 
122
- var html = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
120
+ var html = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
123
121
 
124
122
  // SVG
125
123
  var svg = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'audio', 'canvas', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'video', 'view', 'vkern']);
@@ -207,7 +205,7 @@ function createDOMPurify() {
207
205
  * Version label, exposed for easier checks
208
206
  * if DOMPurify is up to date or not
209
207
  */
210
- DOMPurify.version = '2.1.0';
208
+ DOMPurify.version = '2.2.2';
211
209
 
212
210
  /**
213
211
  * Array of elements that DOMPurify removed during sanitation.
@@ -224,7 +222,6 @@ function createDOMPurify() {
224
222
  }
225
223
 
226
224
  var originalDocument = window.document;
227
- var removeTitle = false;
228
225
 
229
226
  var document = window.document;
230
227
  var DocumentFragment = window.DocumentFragment,
@@ -340,8 +337,13 @@ function createDOMPurify() {
340
337
  /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
341
338
  * `Node` is imported into the current `Document`. If this flag is not enabled the
342
339
  * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
343
- * DOMPurify. */
344
- var RETURN_DOM_IMPORT = false;
340
+ * DOMPurify.
341
+ *
342
+ * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false`
343
+ * might cause XSS from attacks hidden in closed shadowroots in case the browser
344
+ * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/
345
+ */
346
+ var RETURN_DOM_IMPORT = true;
345
347
 
346
348
  /* Try to return a Trusted Type object instead of a string, return a string in
347
349
  * case Trusted Types are not supported */
@@ -413,7 +415,7 @@ function createDOMPurify() {
413
415
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
414
416
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
415
417
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
416
- RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false
418
+ RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
417
419
  RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
418
420
  FORCE_BODY = cfg.FORCE_BODY || false; // Default false
419
421
  SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
@@ -563,11 +565,6 @@ function createDOMPurify() {
563
565
  doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
564
566
  } catch (_) {}
565
567
 
566
- /* Remove title to fix a mXSS bug in older MS Edge */
567
- if (removeTitle) {
568
- addToSet(FORBID_TAGS, ['title']);
569
- }
570
-
571
568
  /* Use createHTMLDocument in case DOMParser is not available */
572
569
  if (!doc || !doc.documentElement) {
573
570
  doc = implementation.createHTMLDocument('');
@@ -586,18 +583,6 @@ function createDOMPurify() {
586
583
  return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
587
584
  };
588
585
 
589
- /* Here we test for a broken feature in Edge that might cause mXSS */
590
- if (DOMPurify.isSupported) {
591
- (function () {
592
- try {
593
- var doc = _initDocument('<x/><title>&lt;/title&gt;&lt;img&gt;');
594
- if (regExpTest(/<\/title/, doc.querySelector('title').innerHTML)) {
595
- removeTitle = true;
596
- }
597
- } catch (_) {}
598
- })();
599
- }
600
-
601
586
  /**
602
587
  * _createIterator
603
588
  *
@@ -693,6 +678,12 @@ function createDOMPurify() {
693
678
  allowedTags: ALLOWED_TAGS
694
679
  });
695
680
 
681
+ /* Take care of an mXSS pattern using p, br inside svg, math */
682
+ if ((tagName === 'svg' || tagName === 'math') && currentNode.querySelectorAll('p, br, form, table').length !== 0) {
683
+ _forceRemove(currentNode);
684
+ return true;
685
+ }
686
+
696
687
  /* Detect mXSS attempts abusing namespace confusion */
697
688
  if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[!/\w]/g, currentNode.innerHTML) && regExpTest(/<[!/\w]/g, currentNode.textContent)) {
698
689
  _forceRemove(currentNode);
@@ -781,7 +772,6 @@ function createDOMPurify() {
781
772
  var attr = void 0;
782
773
  var value = void 0;
783
774
  var lcName = void 0;
784
- var idAttr = void 0;
785
775
  var l = void 0;
786
776
  /* Execute a hook if present */
787
777
  _executeHook('beforeSanitizeAttributes', currentNode, null);
@@ -825,32 +815,7 @@ function createDOMPurify() {
825
815
  }
826
816
 
827
817
  /* Remove attribute */
828
- // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to
829
- // remove a "name" attribute from an <img> tag that has an "id"
830
- // attribute at the time.
831
- if (lcName === 'name' && currentNode.nodeName === 'IMG' && attributes.id) {
832
- idAttr = attributes.id;
833
- attributes = arraySlice(attributes, []);
834
- _removeAttribute('id', currentNode);
835
- _removeAttribute(name, currentNode);
836
- if (arrayIndexOf(attributes, idAttr) > l) {
837
- currentNode.setAttribute('id', idAttr.value);
838
- }
839
- } else if (
840
- // This works around a bug in Safari, where input[type=file]
841
- // cannot be dynamically set after type has been removed
842
- currentNode.nodeName === 'INPUT' && lcName === 'type' && value === 'file' && hookEvent.keepAttr && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) {
843
- continue;
844
- } else {
845
- // This avoids a crash in Safari v9.0 with double-ids.
846
- // The trick is to first set the id to be empty and then to
847
- // remove the attribute
848
- if (name === 'id') {
849
- currentNode.setAttribute(name, '');
850
- }
851
-
852
- _removeAttribute(name, currentNode);
853
- }
818
+ _removeAttribute(name, currentNode);
854
819
 
855
820
  /* Did the hooks approve of the attribute? */
856
821
  if (!hookEvent.keepAttr) {
@@ -991,7 +956,7 @@ function createDOMPurify() {
991
956
  if (IN_PLACE) ; else if (dirty instanceof Node) {
992
957
  /* If dirty is a DOM element, append to an empty document to avoid
993
958
  elements being stripped by the parser */
994
- body = _initDocument('<!-->');
959
+ body = _initDocument('<!---->');
995
960
  importedNode = body.ownerDocument.importNode(dirty, true);
996
961
  if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
997
962
  /* Node is already a body, use as is */