dompurify 3.3.3 → 3.4.0

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
@@ -1,22 +1,24 @@
1
1
  # DOMPurify
2
2
 
3
- [![npm](https://badge.fury.io/js/dompurify.svg)](http://badge.fury.io/js/dompurify) ![Tests](https://github.com/cure53/DOMPurify/workflows/Build%20and%20Test/badge.svg) [![Downloads](https://img.shields.io/npm/dm/dompurify.svg)](https://www.npmjs.com/package/dompurify) ![npm package minimized gzipped size (select exports)](https://img.shields.io/bundlejs/size/dompurify?color=%233C1&label=gzip) [![dependents](https://badgen.net/github/dependents-repo/cure53/dompurify?color=green&label=dependents)](https://github.com/cure53/DOMPurify/network/dependents) [![Cloudback](https://app.cloudback.it/badge/cure53/DOMPurify)](https://cloudback.it)
3
+ [![npm](https://badge.fury.io/js/dompurify.svg)](http://badge.fury.io/js/dompurify) ![Tests](https://github.com/cure53/DOMPurify/workflows/Build%20&%20Test/badge.svg) [![Downloads](https://img.shields.io/npm/dm/dompurify.svg)](https://www.npmjs.com/package/dompurify) [![dependents](https://badgen.net/github/dependents-repo/cure53/dompurify?color=green&label=dependents)](https://github.com/cure53/DOMPurify/network/dependents) [![License](https://img.shields.io/badge/license-MPL--2.0%20OR%20Apache--2.0-blue.svg)](https://github.com/cure53/DOMPurify/blob/main/LICENSE)
4
+
5
+ ![npm package minimized gzipped size (select exports)](https://img.shields.io/bundlejs/size/dompurify?color=%233C1&label=gzip) [![Cloudback](https://app.cloudback.it/badge/cure53/DOMPurify)](https://cloudback.it) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12162/badge)](https://www.bestpractices.dev/projects/12162) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/cure53/DOMPurify/badge)](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMPurify)
4
6
 
5
7
  DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
6
8
 
7
- 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 **v3.3.3**.
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 **v3.4.0**.
8
10
 
9
11
  DOMPurify runs as JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Edge, Firefox and Chrome - as well as almost anything else using Blink, Gecko or WebKit). It doesn't break on MSIE or other legacy browsers. It simply does nothing.
10
12
 
11
13
  **Note that [DOMPurify v2.5.9](https://github.com/cure53/DOMPurify/releases/tag/2.5.9) is the latest version supporting MSIE. For important security updates compatible with MSIE, please use the [2.x branch](https://github.com/cure53/DOMPurify/tree/2.x).**
12
14
 
13
- Our automated tests cover [28 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 v20.x, v22.x, 24.x and v25.x, running DOMPurify on [jsdom](https://github.com/jsdom/jsdom). Older Node versions are known to work as well, but hey... no guarantees.
15
+ Our automated tests cover [12 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L3). We also cover Node.js v20.x, v22.x, 24.x and v25.x, running DOMPurify on [jsdom](https://github.com/jsdom/jsdom). Older Node versions are known to work as well, but hey... no guarantees.
14
16
 
15
17
  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
18
 
17
19
  ## What does it do?
18
20
 
19
- DOMPurify sanitizes HTML and prevents XSS attacks. You can feed DOMPurify with string full of dirty HTML and it will return a string (unless configured otherwise) with clean HTML. DOMPurify will strip out everything that contains dangerous HTML and thereby prevent XSS attacks and other nastiness. It's also damn bloody fast. We use the technologies the browser provides and turn them into an XSS filter. The faster your browser, the faster DOMPurify will be.
21
+ DOMPurify sanitizes HTML and prevents XSS attacks. You can feed DOMPurify with e.g. a string full of dirty HTML and it will return a string (unless configured otherwise) with clean HTML. DOMPurify will strip out everything that contains dangerous HTML and thereby prevent XSS attacks and other nastiness. It's also damn bloody fast. We use the technologies the browser provides and turn them into an XSS filter. The faster your browser, the faster DOMPurify will be.
20
22
 
21
23
  ## How do I use it?
22
24
 
@@ -1,6 +1,6 @@
1
- /*! @license DOMPurify 3.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.3/LICENSE */
1
+ /*! @license DOMPurify 3.4.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.0/LICENSE */
2
2
 
3
- import { TrustedTypePolicy, TrustedHTML, TrustedTypesWindow } from 'trusted-types/lib/index.js';
3
+ import { TrustedTypePolicy, TrustedTypesWindow, TrustedHTML } from 'trusted-types/lib/index.js';
4
4
 
5
5
  /**
6
6
  * Configuration to control DOMPurify behavior.
@@ -444,7 +444,8 @@ type WindowLike = Pick<typeof globalThis, 'DocumentFragment' | 'HTMLTemplateElem
444
444
  MozNamedAttrMap?: typeof window.NamedNodeMap;
445
445
  } & Pick<TrustedTypesWindow, 'trustedTypes'>;
446
446
 
447
- export { type Config, type DOMPurify, type DocumentFragmentHook, type ElementHook, type HookName, type NodeHook, type RemovedAttribute, type RemovedElement, type UponSanitizeAttributeHook, type UponSanitizeAttributeHookEvent, type UponSanitizeElementHook, type UponSanitizeElementHookEvent, type WindowLike };
447
+ export { _default as default };
448
+ export type { Config, DOMPurify, DocumentFragmentHook, ElementHook, HookName, NodeHook, RemovedAttribute, RemovedElement, UponSanitizeAttributeHook, UponSanitizeAttributeHookEvent, UponSanitizeElementHook, UponSanitizeElementHookEvent, WindowLike };
448
449
 
449
450
  // @ts-ignore
450
451
  export = _default;
@@ -1,4 +1,4 @@
1
- /*! @license DOMPurify 3.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.3/LICENSE */
1
+ /*! @license DOMPurify 3.4.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.0/LICENSE */
2
2
 
3
3
  'use strict';
4
4
 
@@ -201,7 +201,7 @@ const text = freeze(['#text']);
201
201
 
202
202
  const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
203
203
  const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
204
- const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
204
+ const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
205
205
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
206
206
 
207
207
  // eslint-disable-next-line unicorn/better-regex
@@ -236,20 +236,11 @@ var EXPRESSIONS = /*#__PURE__*/Object.freeze({
236
236
  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
237
237
  const NODE_TYPE = {
238
238
  element: 1,
239
- attribute: 2,
240
239
  text: 3,
241
- cdataSection: 4,
242
- entityReference: 5,
243
- // Deprecated
244
- entityNode: 6,
245
240
  // Deprecated
246
241
  progressingInstruction: 7,
247
242
  comment: 8,
248
- document: 9,
249
- documentType: 10,
250
- documentFragment: 11,
251
- notation: 12 // Deprecated
252
- };
243
+ document: 9};
253
244
  const getGlobal = function getGlobal() {
254
245
  return typeof window === 'undefined' ? null : window;
255
246
  };
@@ -307,7 +298,7 @@ const _createHooksMap = function _createHooksMap() {
307
298
  function createDOMPurify() {
308
299
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
309
300
  const DOMPurify = root => createDOMPurify(root);
310
- DOMPurify.version = '3.3.3';
301
+ DOMPurify.version = '3.4.0';
311
302
  DOMPurify.removed = [];
312
303
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
313
304
  // Not running in a browser, provide a factory function
@@ -583,7 +574,7 @@ function createDOMPurify() {
583
574
  NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
584
575
  MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
585
576
  HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
586
- CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
577
+ CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || create(null);
587
578
  if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
588
579
  CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
589
580
  }
@@ -623,13 +614,10 @@ function createDOMPurify() {
623
614
  addToSet(ALLOWED_ATTR, xml);
624
615
  }
625
616
  }
626
- /* Prevent function-based ADD_ATTR / ADD_TAGS from leaking across calls */
627
- if (!objectHasOwnProperty(cfg, 'ADD_TAGS')) {
628
- EXTRA_ELEMENT_HANDLING.tagCheck = null;
629
- }
630
- if (!objectHasOwnProperty(cfg, 'ADD_ATTR')) {
631
- EXTRA_ELEMENT_HANDLING.attributeCheck = null;
632
- }
617
+ /* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent
618
+ * leaking across calls when switching from function to array config */
619
+ EXTRA_ELEMENT_HANDLING.tagCheck = null;
620
+ EXTRA_ELEMENT_HANDLING.attributeCheck = null;
633
621
  /* Merge configuration parameters */
634
622
  if (cfg.ADD_TAGS) {
635
623
  if (typeof cfg.ADD_TAGS === 'function') {
@@ -952,6 +940,11 @@ function createDOMPurify() {
952
940
  _forceRemove(currentNode);
953
941
  return true;
954
942
  }
943
+ /* Remove risky CSS construction leading to mXSS */
944
+ if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
945
+ _forceRemove(currentNode);
946
+ return true;
947
+ }
955
948
  /* Remove any occurrence of processing instructions */
956
949
  if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
957
950
  _forceRemove(currentNode);
@@ -963,7 +956,7 @@ function createDOMPurify() {
963
956
  return true;
964
957
  }
965
958
  /* Remove element if anything forbids its presence */
966
- if (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName])) {
959
+ if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
967
960
  /* Check if we have a custom element to handle */
968
961
  if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
969
962
  if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
@@ -1202,7 +1195,7 @@ function createDOMPurify() {
1202
1195
  *
1203
1196
  * @param fragment to iterate over recursively
1204
1197
  */
1205
- const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1198
+ const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) {
1206
1199
  let shadowNode = null;
1207
1200
  const shadowIterator = _createNodeIterator(fragment);
1208
1201
  /* Execute a hook if present */
@@ -1216,7 +1209,7 @@ function createDOMPurify() {
1216
1209
  _sanitizeAttributes(shadowNode);
1217
1210
  /* Deep shadow DOM detected */
1218
1211
  if (shadowNode.content instanceof DocumentFragment) {
1219
- _sanitizeShadowDOM(shadowNode.content);
1212
+ _sanitizeShadowDOM2(shadowNode.content);
1220
1213
  }
1221
1214
  }
1222
1215
  /* Execute a hook if present */
@@ -1311,7 +1304,7 @@ function createDOMPurify() {
1311
1304
  _sanitizeAttributes(currentNode);
1312
1305
  /* Shadow DOM detected, sanitize it */
1313
1306
  if (currentNode.content instanceof DocumentFragment) {
1314
- _sanitizeShadowDOM(currentNode.content);
1307
+ _sanitizeShadowDOM2(currentNode.content);
1315
1308
  }
1316
1309
  }
1317
1310
  /* If we sanitized `dirty` in-place, return it. */
@@ -1320,6 +1313,14 @@ function createDOMPurify() {
1320
1313
  }
1321
1314
  /* Return sanitized string or DOM */
1322
1315
  if (RETURN_DOM) {
1316
+ if (SAFE_FOR_TEMPLATES) {
1317
+ body.normalize();
1318
+ let html = body.innerHTML;
1319
+ arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1320
+ html = stringReplace(html, expr, ' ');
1321
+ });
1322
+ body.innerHTML = html;
1323
+ }
1323
1324
  if (RETURN_DOM_FRAGMENT) {
1324
1325
  returnNode = createDocumentFragment.call(body.ownerDocument);
1325
1326
  while (body.firstChild) {