dompurify 3.0.11 → 3.1.1
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/LICENSE +1 -1
- package/README.md +21 -6
- package/dist/purify.cjs.js +64 -5
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.es.mjs +64 -5
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +64 -5
- package/dist/purify.js.map +1 -1
- package/dist/purify.min.js +2 -2
- package/dist/purify.min.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
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 **v3.
|
|
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.1.1**.
|
|
10
10
|
|
|
11
11
|
DOMPurify is written in 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.
|
|
12
12
|
|
|
13
|
-
**Note that [DOMPurify v2.
|
|
13
|
+
**Note that [DOMPurify v2.5.1](https://github.com/cure53/DOMPurify/releases/tag/2.5.1) 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).**
|
|
14
14
|
|
|
15
15
|
Our automated tests cover [19 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 v16.x, v17.x, v18.x and v19.x, running DOMPurify on [jsdom](https://github.com/jsdom/jsdom). Older Node versions are known to work as well, but hey... no guarantees.
|
|
16
16
|
|
|
@@ -77,6 +77,8 @@ Running DOMPurify on the server requires a DOM to be present, which is probably
|
|
|
77
77
|
|
|
78
78
|
Why? Because older versions of _jsdom_ are known to be buggy in ways that result in XSS _even if_ DOMPurify does everything 100% correctly. There are **known attack vectors** in, e.g. _jsdom v19.0.0_ that are fixed in _jsdom v20.0.0_ - and we really recommend to keep _jsdom_ up to date because of that.
|
|
79
79
|
|
|
80
|
+
Please also be aware that tools like [happy-dom](https://github.com/capricorn86/happy-dom) exist but **are not considered safe** at this point. Combining DOMPurify with _happy-dom_ is currently not recommended and will likely lead to XSS.
|
|
81
|
+
|
|
80
82
|
Other than that, you are fine to use DOMPurify on the server. Probably. This really depends on _jsdom_ or whatever DOM you utilize server-side. If you can live with that, this is how you get it to work:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
@@ -156,6 +158,15 @@ In version 2.0.0, a config flag was added to control DOMPurify's behavior regard
|
|
|
156
158
|
|
|
157
159
|
When `DOMPurify.sanitize` is used in an environment where the Trusted Types API is available and `RETURN_TRUSTED_TYPE` is set to `true`, it tries to return a `TrustedHTML` value instead of a string (the behavior for `RETURN_DOM` and `RETURN_DOM_FRAGMENT` config options does not change).
|
|
158
160
|
|
|
161
|
+
Note that in order to create a policy in `trustedTypes` using DOMPurify, `RETURN_TRUSTED_TYPE: false` is required, as `createHTML` expects a normal string, not `TrustedHTML`. The example below shows this.
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
window.trustedTypes!.createPolicy('default', {
|
|
165
|
+
createHTML: (to_escape) =>
|
|
166
|
+
DOMPurify.sanitize(to_escape, { RETURN_TRUSTED_TYPE: false }),
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
159
170
|
## Can I configure DOMPurify?
|
|
160
171
|
|
|
161
172
|
Yes. The included default configuration values are pretty good already - but you can of course override them. Check out the [`/demos`](https://github.com/cure53/DOMPurify/tree/main/demos) folder to see a bunch of examples on how you can [customize DOMPurify](https://github.com/cure53/DOMPurify/tree/main/demos#what-is-this).
|
|
@@ -167,6 +178,10 @@ Yes. The included default configuration values are pretty good already - but you
|
|
|
167
178
|
// allowing template parsing in user-controlled HTML is not advised at all.
|
|
168
179
|
// only use this mode if there is really no alternative.
|
|
169
180
|
const clean = DOMPurify.sanitize(dirty, {SAFE_FOR_TEMPLATES: true});
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
// change how e.g. comments containing risky HTML characters are treated.
|
|
184
|
+
const clean = DOMPurify.sanitize(dirty, {SAFE_FOR_XML: false});
|
|
170
185
|
```
|
|
171
186
|
|
|
172
187
|
### Control our allow-lists and block-lists
|
|
@@ -354,11 +369,11 @@ _Example_:
|
|
|
354
369
|
|
|
355
370
|
```js
|
|
356
371
|
DOMPurify.addHook(
|
|
357
|
-
'
|
|
372
|
+
'uponSanitizeAttribute',
|
|
358
373
|
function (currentNode, hookEvent, config) {
|
|
359
|
-
// Do something with the current node
|
|
360
|
-
// You can also mutate hookEvent (i.e. set hookEvent.forceKeepAttr = true)
|
|
361
|
-
|
|
374
|
+
// Do something with the current node
|
|
375
|
+
// You can also mutate hookEvent for current node (i.e. set hookEvent.forceKeepAttr = true)
|
|
376
|
+
// For other than 'uponSanitizeAttribute' hook types hookEvent equals to null
|
|
362
377
|
}
|
|
363
378
|
);
|
|
364
379
|
```
|
package/dist/purify.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.
|
|
1
|
+
/*! @license DOMPurify 3.1.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.1/LICENSE */
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -198,7 +198,7 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
198
198
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
199
199
|
const text = freeze(['#text']);
|
|
200
200
|
|
|
201
|
-
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', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', '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', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns', 'slot']);
|
|
201
|
+
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', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', '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', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
|
|
202
202
|
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', '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', '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', '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', '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', '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', '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']);
|
|
203
203
|
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
204
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
@@ -284,7 +284,7 @@ function createDOMPurify() {
|
|
|
284
284
|
* Version label, exposed for easier checks
|
|
285
285
|
* if DOMPurify is up to date or not
|
|
286
286
|
*/
|
|
287
|
-
DOMPurify.version = '3.
|
|
287
|
+
DOMPurify.version = '3.1.1';
|
|
288
288
|
|
|
289
289
|
/**
|
|
290
290
|
* Array of elements that DOMPurify removed during sanitation.
|
|
@@ -426,6 +426,11 @@ function createDOMPurify() {
|
|
|
426
426
|
*/
|
|
427
427
|
let SAFE_FOR_TEMPLATES = false;
|
|
428
428
|
|
|
429
|
+
/* Output should be safe even for XML used within HTML and alike.
|
|
430
|
+
* This means, DOMPurify removes comments when containing risky content.
|
|
431
|
+
*/
|
|
432
|
+
let SAFE_FOR_XML = true;
|
|
433
|
+
|
|
429
434
|
/* Decide if document with <html>... should be returned */
|
|
430
435
|
let WHOLE_DOCUMENT = false;
|
|
431
436
|
|
|
@@ -512,6 +517,9 @@ function createDOMPurify() {
|
|
|
512
517
|
/* Keep a reference to config to pass to hooks */
|
|
513
518
|
let CONFIG = null;
|
|
514
519
|
|
|
520
|
+
/* Specify the maximum element nesting depth to prevent mXSS */
|
|
521
|
+
const MAX_NESTING_DEPTH = 255;
|
|
522
|
+
|
|
515
523
|
/* Ideally, do not touch anything below this line */
|
|
516
524
|
/* ______________________________________________ */
|
|
517
525
|
|
|
@@ -573,6 +581,7 @@ function createDOMPurify() {
|
|
|
573
581
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
574
582
|
ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
|
|
575
583
|
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
|
|
584
|
+
SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
|
|
576
585
|
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
|
|
577
586
|
RETURN_DOM = cfg.RETURN_DOM || false; // Default false
|
|
578
587
|
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
|
|
@@ -921,7 +930,11 @@ function createDOMPurify() {
|
|
|
921
930
|
* @return {Boolean} true if clobbered, false if safe
|
|
922
931
|
*/
|
|
923
932
|
const _isClobbered = function _isClobbered(elm) {
|
|
924
|
-
return elm instanceof HTMLFormElement && (
|
|
933
|
+
return elm instanceof HTMLFormElement && (
|
|
934
|
+
// eslint-disable-next-line unicorn/no-typeof-undefined
|
|
935
|
+
typeof elm.__depth !== 'undefined' && typeof elm.__depth !== 'number' ||
|
|
936
|
+
// eslint-disable-next-line unicorn/no-typeof-undefined
|
|
937
|
+
typeof elm.__removalCount !== 'undefined' && typeof elm.__removalCount !== 'number' || typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
|
|
925
938
|
};
|
|
926
939
|
|
|
927
940
|
/**
|
|
@@ -994,6 +1007,12 @@ function createDOMPurify() {
|
|
|
994
1007
|
return true;
|
|
995
1008
|
}
|
|
996
1009
|
|
|
1010
|
+
/* Remove any kind of possibly harmful comments */
|
|
1011
|
+
if (SAFE_FOR_XML && currentNode.nodeType === 8 && regExpTest(/<[/\w]/g, currentNode.data)) {
|
|
1012
|
+
_forceRemove(currentNode);
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
997
1016
|
/* Remove element if anything forbids its presence */
|
|
998
1017
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
999
1018
|
/* Check if we have a custom element to handle */
|
|
@@ -1013,7 +1032,9 @@ function createDOMPurify() {
|
|
|
1013
1032
|
if (childNodes && parentNode) {
|
|
1014
1033
|
const childCount = childNodes.length;
|
|
1015
1034
|
for (let i = childCount - 1; i >= 0; --i) {
|
|
1016
|
-
|
|
1035
|
+
const childClone = cloneNode(childNodes[i], true);
|
|
1036
|
+
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
1037
|
+
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
1017
1038
|
}
|
|
1018
1039
|
}
|
|
1019
1040
|
}
|
|
@@ -1246,8 +1267,27 @@ function createDOMPurify() {
|
|
|
1246
1267
|
continue;
|
|
1247
1268
|
}
|
|
1248
1269
|
|
|
1270
|
+
/* Set the nesting depth of an element */
|
|
1271
|
+
if (shadowNode.nodeType === 1) {
|
|
1272
|
+
if (shadowNode.parentNode && shadowNode.parentNode.__depth) {
|
|
1273
|
+
/*
|
|
1274
|
+
We want the depth of the node in the original tree, which can
|
|
1275
|
+
change when it's removed from its parent.
|
|
1276
|
+
*/
|
|
1277
|
+
shadowNode.__depth = (shadowNode.__removalCount || 0) + shadowNode.parentNode.__depth + 1;
|
|
1278
|
+
} else {
|
|
1279
|
+
shadowNode.__depth = 1;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/* Remove an element if nested too deeply to avoid mXSS */
|
|
1284
|
+
if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
|
|
1285
|
+
_forceRemove(shadowNode);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1249
1288
|
/* Deep shadow DOM detected */
|
|
1250
1289
|
if (shadowNode.content instanceof DocumentFragment) {
|
|
1290
|
+
shadowNode.content.__depth = shadowNode.__depth;
|
|
1251
1291
|
_sanitizeShadowDOM(shadowNode.content);
|
|
1252
1292
|
}
|
|
1253
1293
|
|
|
@@ -1364,8 +1404,27 @@ function createDOMPurify() {
|
|
|
1364
1404
|
continue;
|
|
1365
1405
|
}
|
|
1366
1406
|
|
|
1407
|
+
/* Set the nesting depth of an element */
|
|
1408
|
+
if (currentNode.nodeType === 1) {
|
|
1409
|
+
if (currentNode.parentNode && currentNode.parentNode.__depth) {
|
|
1410
|
+
/*
|
|
1411
|
+
We want the depth of the node in the original tree, which can
|
|
1412
|
+
change when it's removed from its parent.
|
|
1413
|
+
*/
|
|
1414
|
+
currentNode.__depth = (currentNode.__removalCount || 0) + currentNode.parentNode.__depth + 1;
|
|
1415
|
+
} else {
|
|
1416
|
+
currentNode.__depth = 1;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/* Remove an element if nested too deeply to avoid mXSS */
|
|
1421
|
+
if (currentNode.__depth >= MAX_NESTING_DEPTH) {
|
|
1422
|
+
_forceRemove(currentNode);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1367
1425
|
/* Shadow DOM detected, sanitize it */
|
|
1368
1426
|
if (currentNode.content instanceof DocumentFragment) {
|
|
1427
|
+
currentNode.content.__depth = currentNode.__depth;
|
|
1369
1428
|
_sanitizeShadowDOM(currentNode.content);
|
|
1370
1429
|
}
|
|
1371
1430
|
|