dompurify 3.4.11 → 3.4.12
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 +3 -3
- package/dist/purify.cjs.d.ts +1 -1
- package/dist/purify.cjs.js +187 -11
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.es.d.mts +1 -1
- package/dist/purify.es.mjs +187 -11
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +187 -11
- 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 +6 -6
- package/src/attrs.ts +2 -0
- package/src/purify.ts +219 -8
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/dompurify) [](https://github.com/cure53/DOMPurify/blob/main/LICENSE) [](https://www.npmjs.com/package/dompurify) [](https://github.com/cure53/DOMPurify/network/dependents)  [](https://cloudback.it)
|
|
4
4
|
|
|
5
|
-
[](https://www.bestpractices.dev/projects/12162) [](https://github.com/cure53/DOMPurify/actions/workflows/build-and-test.yml) [](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMPurify) [](https://badge.socket.dev/npm/package/dompurify/latest) [](https://www.bestpractices.dev/projects/12162) [](https://github.com/cure53/DOMPurify/actions/workflows/build-and-test.yml) [](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMPurify) [](https://badge.socket.dev/npm/package/dompurify/latest) [](https://security.snyk.io/package/npm/dompurify)
|
|
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.4.
|
|
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.12**.
|
|
10
10
|
|
|
11
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.
|
|
12
12
|
|
|
@@ -565,4 +565,4 @@ Feature releases will not be announced to this list.
|
|
|
565
565
|
|
|
566
566
|
Many people have helped DOMPurify become what it is today, and they deserve to be acknowledged!
|
|
567
567
|
|
|
568
|
-
[offset](https://github.com/offset), [Bankde](https://github.com/Bankde), [lukewarlow](https://github.com/lukewarlow), [DEMON1A](https://github.com/DEMON1A), [fg0x0](https://github.com/fg0x0), [kodareef5](https://github.com/kodareef5), [DavidOliver](https://github.com/DavidOliver), [1Jesper1](https://github.com/1Jesper1), [bencalif](https://github.com/bencalif), [trace37labs](https://github.com/trace37labs), [eddieran](https://github.com/eddieran), [christos-eth](https://github.com/christos-eth), [researchatfluidattacks](https://github.com/researchatfluidattacks), [frevadiscor](https://github.com/frevadiscor), [Rotzbua](https://github.com/Rotzbua), [binhpv](https://github.com/binhpv), [MariusRumpf](https://github.com/MariusRumpf), [prasadrajandran](https://github.com/prasadrajandran), [Cybozu 💛💸](https://github.com/cybozu), [hata6502 💸](https://github.com/hata6502), [openclaw 💸](https://github.com/openclaw), [intra-mart-dh 💸](https://github.com/intra-mart-dh), [nelstrom ❤️](https://github.com/nelstrom), [hash_kitten ❤️](https://twitter.com/hash_kitten), [kevin_mizu ❤️](https://twitter.com/kevin_mizu), [icesfont ❤️](https://github.com/icesfont), [reduckted ❤️](https://github.com/reduckted), [dcramer 💸](https://github.com/dcramer), [JGraph 💸](https://github.com/jgraph), [baekilda 💸](https://github.com/baekilda), [Healthchecks 💸](https://github.com/healthchecks), [Sentry 💸](https://github.com/getsentry), [jarrodldavis 💸](https://github.com/jarrodldavis), [CynegeticIO](https://github.com/CynegeticIO), [ssi02014 ❤️](https://github.com/ssi02014), [GrantGryczan](https://github.com/GrantGryczan), [Lowdefy](https://twitter.com/lowdefy), [granlem](https://twitter.com/MaximeVeit), [oreoshake](https://github.com/oreoshake), [tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [is2ei](https://github.com/is2ei), [SoheilKhodayari](https://github.com/SoheilKhodayari), [franktopel](https://github.com/franktopel), [NateScarlet](https://github.com/NateScarlet), [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), [@CmdEngineer\_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit), [davecardwell](https://github.com/davecardwell) and especially [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)
|
|
568
|
+
[offset](https://github.com/offset), [Bankde](https://github.com/Bankde), [lukewarlow](https://github.com/lukewarlow), [DEMON1A](https://github.com/DEMON1A), [fg0x0](https://github.com/fg0x0), [kodareef5](https://github.com/kodareef5), [DavidOliver](https://github.com/DavidOliver), [1Jesper1](https://github.com/1Jesper1), [bencalif](https://github.com/bencalif), [trace37labs](https://github.com/trace37labs), [eddieran](https://github.com/eddieran), [christos-eth](https://github.com/christos-eth), [researchatfluidattacks](https://github.com/researchatfluidattacks), [frevadiscor](https://github.com/frevadiscor), [Rotzbua](https://github.com/Rotzbua), [binhpv](https://github.com/binhpv), [MariusRumpf](https://github.com/MariusRumpf), [prasadrajandran](https://github.com/prasadrajandran), [Cybozu 💛💸](https://github.com/cybozu), [hata6502 💸](https://github.com/hata6502), [openclaw 💸](https://github.com/openclaw), [intra-mart-dh 💸](https://github.com/intra-mart-dh), [nelstrom ❤️](https://github.com/nelstrom), [hash_kitten ❤️](https://twitter.com/hash_kitten), [kevin_mizu ❤️](https://twitter.com/kevin_mizu), [icesfont ❤️](https://github.com/icesfont), [reduckted ❤️](https://github.com/reduckted), [dcramer 💸](https://github.com/dcramer), [JGraph 💸](https://github.com/jgraph), [baekilda 💸](https://github.com/baekilda), [Healthchecks 💸](https://github.com/healthchecks), [Sentry 💸](https://github.com/getsentry), [jarrodldavis 💸](https://github.com/jarrodldavis), [CynegeticIO](https://github.com/CynegeticIO), [ssi02014 ❤️](https://github.com/ssi02014), [GrantGryczan](https://github.com/GrantGryczan), [Lowdefy](https://twitter.com/lowdefy), [granlem](https://twitter.com/MaximeVeit), [oreoshake](https://github.com/oreoshake), [tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [is2ei](https://github.com/is2ei), [SoheilKhodayari](https://github.com/SoheilKhodayari), [franktopel](https://github.com/franktopel), [NateScarlet](https://github.com/NateScarlet), [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), [@CmdEngineer\_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit), [davecardwell](https://github.com/davecardwell), [Develop-KIM](https://github.com/Develop-KIM), [asamuzaK](https://github.com/asamuzaK), [fishjojo1](https://github.com/fishjojo1), [Rikuxx0](https://github.com/Rikuxx0) and especially [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)
|
package/dist/purify.cjs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.4.
|
|
1
|
+
/*! @license DOMPurify 3.4.12 | (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.12/LICENSE */
|
|
2
2
|
|
|
3
3
|
import { TrustedTypePolicy, TrustedTypesWindow, TrustedHTML } from 'trusted-types/lib/index.js';
|
|
4
4
|
|
package/dist/purify.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.4.
|
|
1
|
+
/*! @license DOMPurify 3.4.12 | (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.12/LICENSE */
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -313,7 +313,7 @@ const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongd
|
|
|
313
313
|
const text = freeze(['#text']);
|
|
314
314
|
|
|
315
315
|
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', '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']);
|
|
316
|
-
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']);
|
|
316
|
+
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', 'dominant-baseline', '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-orientation', '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']);
|
|
317
317
|
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']);
|
|
318
318
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
319
319
|
|
|
@@ -426,7 +426,7 @@ const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options
|
|
|
426
426
|
function createDOMPurify() {
|
|
427
427
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
428
428
|
const DOMPurify = root => createDOMPurify(root);
|
|
429
|
-
DOMPurify.version = '3.4.
|
|
429
|
+
DOMPurify.version = '3.4.12';
|
|
430
430
|
DOMPurify.removed = [];
|
|
431
431
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
432
432
|
// Not running in a browser, provide a factory function
|
|
@@ -1108,6 +1108,13 @@ function createDOMPurify() {
|
|
|
1108
1108
|
* @param root the in-place root to empty
|
|
1109
1109
|
*/
|
|
1110
1110
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
1111
|
+
/* Strip every disallowed attribute (on* handlers included) off the whole
|
|
1112
|
+
subtree BEFORE detaching anything. Detaching first would hand back
|
|
1113
|
+
handler-bearing originals (e.g. an already-loading `<img onerror>`)
|
|
1114
|
+
whose queued resource event still fires in page scope after we throw.
|
|
1115
|
+
Clobber-safe reads; a doomed clobbered node's own attributes are
|
|
1116
|
+
irrelevant while its non-clobbered descendants are reached and scrubbed. */
|
|
1117
|
+
_neutralizeSubtree(root);
|
|
1111
1118
|
const childNodes = getChildNodes(root);
|
|
1112
1119
|
if (childNodes) {
|
|
1113
1120
|
const snapshot = [];
|
|
@@ -1235,6 +1242,82 @@ function createDOMPurify() {
|
|
|
1235
1242
|
}
|
|
1236
1243
|
}
|
|
1237
1244
|
};
|
|
1245
|
+
/**
|
|
1246
|
+
* _neutralizePatchLinkage
|
|
1247
|
+
*
|
|
1248
|
+
* IN_PLACE entry pre-pass (declarative-partial-updates / streaming
|
|
1249
|
+
* hardening, https://github.com/WICG/declarative-partial-updates).
|
|
1250
|
+
*
|
|
1251
|
+
* The main walk strips patch linkage (`for`/`patchsrc`) and removes range
|
|
1252
|
+
* markers (PIs / markup comments) node-by-node, in document order, AS it
|
|
1253
|
+
* reaches each node. On a live in-place root that leaves a window: from the
|
|
1254
|
+
* moment the root is connected until the walk arrives at a given node, that
|
|
1255
|
+
* node's linkage is live. A patch applied on connection/stream can fire as
|
|
1256
|
+
* a microtask during the walk and inject or teleport an unsanitized DOM
|
|
1257
|
+
* range into a region the iterator has already passed and will not revisit,
|
|
1258
|
+
* so the post-return "tree is sanitized" contract is violated. Sweep the
|
|
1259
|
+
* whole tree once up front and sever every linkage before the walk begins,
|
|
1260
|
+
* closing that window.
|
|
1261
|
+
*
|
|
1262
|
+
* This CANNOT undo a patch that already fired before sanitize ran — that is
|
|
1263
|
+
* the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
|
|
1264
|
+
* but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
|
|
1265
|
+
* to group with the rest of the declarative-partial-updates handling and
|
|
1266
|
+
* stay overridable, consistent with the codebase.
|
|
1267
|
+
*
|
|
1268
|
+
* Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
|
|
1269
|
+
* clobbered root cannot defeat the sweep of its non-clobbered descendants.
|
|
1270
|
+
*
|
|
1271
|
+
* NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
|
|
1272
|
+
* .html Q1): this mirrors the existing policy of keeping `for` on
|
|
1273
|
+
* <label>/<output>. If the shipping feature can drive a patch through a
|
|
1274
|
+
* surviving `for`-on-label/output + `id` pair, this pre-pass and the
|
|
1275
|
+
* attribute check at _isBasicCustomElement's caller must additionally drop
|
|
1276
|
+
* that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
|
|
1277
|
+
*
|
|
1278
|
+
* @param root the in-place root to sweep
|
|
1279
|
+
*/
|
|
1280
|
+
const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
|
|
1281
|
+
if (!SAFE_FOR_XML) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
const stack = [root];
|
|
1285
|
+
while (stack.length > 0) {
|
|
1286
|
+
const node = stack.pop();
|
|
1287
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
1288
|
+
/* Remove range markers (the target side of a patch linkage): every
|
|
1289
|
+
processing instruction, and any markup-bearing comment. */
|
|
1290
|
+
if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
|
|
1291
|
+
try {
|
|
1292
|
+
remove(node);
|
|
1293
|
+
} catch (_) {
|
|
1294
|
+
/* Best-effort */
|
|
1295
|
+
}
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
/* Strip patch-source attributes (the source side) off elements. */
|
|
1299
|
+
if (nodeType === NODE_TYPE.element) {
|
|
1300
|
+
const element = node;
|
|
1301
|
+
const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
|
|
1302
|
+
try {
|
|
1303
|
+
if (element.hasAttribute && element.hasAttribute('patchsrc')) {
|
|
1304
|
+
element.removeAttribute('patchsrc');
|
|
1305
|
+
}
|
|
1306
|
+
if (element.hasAttribute && element.hasAttribute('for') && lcTag !== 'label' && lcTag !== 'output') {
|
|
1307
|
+
element.removeAttribute('for');
|
|
1308
|
+
}
|
|
1309
|
+
} catch (_) {
|
|
1310
|
+
/* Clobbered removeAttribute/hasAttribute on a doomed node — ignore */
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const childNodes = getChildNodes(node);
|
|
1314
|
+
if (childNodes) {
|
|
1315
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
1316
|
+
stack.push(childNodes[i]);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1238
1321
|
/**
|
|
1239
1322
|
* _initDocument
|
|
1240
1323
|
*
|
|
@@ -1485,9 +1568,15 @@ function createDOMPurify() {
|
|
|
1485
1568
|
/**
|
|
1486
1569
|
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
1487
1570
|
* allowed custom elements (false return exits _sanitizeElements
|
|
1488
|
-
* early - namespace
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1571
|
+
* early - the namespace and fallback-tag removal checks are
|
|
1572
|
+
* intentionally skipped for kept custom elements), else hoist
|
|
1573
|
+
* content per KEEP_CONTENT and remove.
|
|
1574
|
+
*
|
|
1575
|
+
* A kept custom element is the ONLY case in which this function
|
|
1576
|
+
* returns false, so the caller uses that return value to run the
|
|
1577
|
+
* afterSanitizeElements hook on the kept element and keep the
|
|
1578
|
+
* element-hook lifecycle consistent with normal allowlisted
|
|
1579
|
+
* elements (GHSA-c2j3-45gr-mqc4).
|
|
1491
1580
|
*
|
|
1492
1581
|
* @param currentNode the disallowed node
|
|
1493
1582
|
* @param tagName the node's transformCaseFunc'd tag name
|
|
@@ -1553,9 +1642,15 @@ function createDOMPurify() {
|
|
|
1553
1642
|
* @param currentNode to check for permission to exist
|
|
1554
1643
|
* @return true if node was killed, false if left alive
|
|
1555
1644
|
*/
|
|
1556
|
-
|
|
1645
|
+
// eslint-disable-next-line complexity
|
|
1646
|
+
const _sanitizeElements = function _sanitizeElements(currentNode, root) {
|
|
1557
1647
|
/* Execute a hook if present */
|
|
1558
1648
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1649
|
+
/* A hook may have detached the node — treat it as removed (see the
|
|
1650
|
+
detached-node comment after the uponSanitizeElement hook below). */
|
|
1651
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
1652
|
+
return true;
|
|
1653
|
+
}
|
|
1559
1654
|
/* Check if element is clobbered or can clobber */
|
|
1560
1655
|
if (_isClobbered(currentNode)) {
|
|
1561
1656
|
_forceRemove(currentNode);
|
|
@@ -1568,6 +1663,24 @@ function createDOMPurify() {
|
|
|
1568
1663
|
tagName,
|
|
1569
1664
|
allowedTags: ALLOWED_TAGS
|
|
1570
1665
|
});
|
|
1666
|
+
/* A hook may have detached the node from the tree — a long-standing
|
|
1667
|
+
user pattern (issue #469; draw.io-style foreignObject filtering).
|
|
1668
|
+
Per the cached, unclobberable parentNode getter the node is
|
|
1669
|
+
genuinely out of the tree, so it can reach neither the serialized
|
|
1670
|
+
output nor an IN_PLACE live tree; treat it as removed and stop
|
|
1671
|
+
processing it. Without this guard, the unsafe-node / namespace
|
|
1672
|
+
checks below would call _forceRemove on a parentless node and hit
|
|
1673
|
+
the REPORT-3 fail-closed throw — which exists for nodes DOMPurify
|
|
1674
|
+
wants gone but *cannot* detach (clobbered / parentless roots), the
|
|
1675
|
+
opposite of a node that is already safely gone. The walk root is
|
|
1676
|
+
exempt: a detached IN_PLACE root is legitimate input and must still
|
|
1677
|
+
be fully sanitized, and a kill-decision on it must keep hitting the
|
|
1678
|
+
REPORT-3 throw. Nodes detached by hooks are the hook's
|
|
1679
|
+
responsibility: they are not recorded in DOMPurify.removed and are
|
|
1680
|
+
not neutralized by the post-walk IN_PLACE pass. */
|
|
1681
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
1682
|
+
return true;
|
|
1683
|
+
}
|
|
1571
1684
|
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
1572
1685
|
if (_isUnsafeNode(currentNode, tagName)) {
|
|
1573
1686
|
_forceRemove(currentNode);
|
|
@@ -1575,7 +1688,22 @@ function createDOMPurify() {
|
|
|
1575
1688
|
}
|
|
1576
1689
|
/* Remove element if anything forbids its presence */
|
|
1577
1690
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
1578
|
-
|
|
1691
|
+
const removed = _sanitizeDisallowedNode(currentNode, tagName);
|
|
1692
|
+
/* A false return means the node is a custom element kept via
|
|
1693
|
+
CUSTOM_ELEMENT_HANDLING - the only keep path through
|
|
1694
|
+
_sanitizeDisallowedNode. Run afterSanitizeElements on it so the
|
|
1695
|
+
element-hook lifecycle matches normal allowlisted elements: a
|
|
1696
|
+
security policy applied in this hook (e.g. stripping an attribute
|
|
1697
|
+
from every surviving element) must not silently skip kept custom
|
|
1698
|
+
elements (GHSA-c2j3-45gr-mqc4). This mirrors the normal-element
|
|
1699
|
+
tail below - the hook runs, then the walker's subsequent
|
|
1700
|
+
_sanitizeAttributes pass sanitizes the element's attributes. The
|
|
1701
|
+
deliberately skipped namespace and fallback-tag removal checks stay
|
|
1702
|
+
skipped; they are removal decisions, not the hook contract. */
|
|
1703
|
+
if (removed === false) {
|
|
1704
|
+
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
1705
|
+
}
|
|
1706
|
+
return removed;
|
|
1579
1707
|
}
|
|
1580
1708
|
/* Check whether element has a valid namespace.
|
|
1581
1709
|
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
@@ -1622,6 +1750,33 @@ function createDOMPurify() {
|
|
|
1622
1750
|
if (FORBID_ATTR[lcName]) {
|
|
1623
1751
|
return false;
|
|
1624
1752
|
}
|
|
1753
|
+
/* Reject declarative-partial-updates patch-linkage attributes
|
|
1754
|
+
(https://github.com/WICG/declarative-partial-updates).
|
|
1755
|
+
Empirical note (Chrome 150, verified — see
|
|
1756
|
+
test/declarative-patch-probe-v3.html): expansion is NOT applied after
|
|
1757
|
+
sanitization. For the string path it fires during sanitize()'s own
|
|
1758
|
+
parse, so the walk sees and sanitizes the fully materialized expanded
|
|
1759
|
+
tree — teleports into MathML/SVG integration points included; a
|
|
1760
|
+
weaponized `<template for>`->`<img onerror>` comes back with the handler
|
|
1761
|
+
stripped. For the IN_PLACE path it fires on connection, before the walk.
|
|
1762
|
+
Either way DOMPurify is NOT blind to the patch.
|
|
1763
|
+
This removal is therefore defense-in-depth rather than the sole barrier:
|
|
1764
|
+
it prevents live linkage from surviving into the OUTPUT and re-expanding
|
|
1765
|
+
in the caller's context, and keeps behaviour deterministic if a future
|
|
1766
|
+
engine defers expansion. `for` is legitimate only on <label>/<output>;
|
|
1767
|
+
anywhere else (notably <template for>) it links the element to a patch
|
|
1768
|
+
target and teleports or removes an arbitrary DOM range by id/marker name.
|
|
1769
|
+
`patchsrc` fetches remote markup and is treated as a script-loading
|
|
1770
|
+
mechanism (CSP). Gated on SAFE_FOR_XML so the removal groups with the
|
|
1771
|
+
other structural-threat checks and stays overridable, consistent with
|
|
1772
|
+
the rest of the codebase. PI range markers are already removed by
|
|
1773
|
+
_isUnsafeNode. */
|
|
1774
|
+
if (SAFE_FOR_XML && lcName === 'patchsrc') {
|
|
1775
|
+
return false;
|
|
1776
|
+
}
|
|
1777
|
+
if (SAFE_FOR_XML && lcName === 'for' && lcTag !== 'label' && lcTag !== 'output') {
|
|
1778
|
+
return false;
|
|
1779
|
+
}
|
|
1625
1780
|
/* Make sure attribute cannot clobber */
|
|
1626
1781
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1627
1782
|
return false;
|
|
@@ -1831,7 +1986,7 @@ function createDOMPurify() {
|
|
|
1831
1986
|
/* Execute a hook if present */
|
|
1832
1987
|
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
1833
1988
|
/* Sanitize tags and elements */
|
|
1834
|
-
_sanitizeElements(shadowNode);
|
|
1989
|
+
_sanitizeElements(shadowNode, fragment);
|
|
1835
1990
|
/* Check attributes next */
|
|
1836
1991
|
_sanitizeAttributes(shadowNode);
|
|
1837
1992
|
/* Deep shadow DOM detected.
|
|
@@ -2022,6 +2177,11 @@ function createDOMPurify() {
|
|
|
2022
2177
|
keep using and whose return value they ignore — unsanitized. REPORT-2. */
|
|
2023
2178
|
const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
|
|
2024
2179
|
if (inPlace) {
|
|
2180
|
+
/* Declarative-partial-updates / streaming pre-pass: sever every patch
|
|
2181
|
+
linkage across the live tree BEFORE the walk, so no patch can fire
|
|
2182
|
+
mid-walk and inject into an already-processed region. Runs first, so
|
|
2183
|
+
it also covers the forbidden/clobbered roots that throw below. */
|
|
2184
|
+
_neutralizePatchLinkage(dirty);
|
|
2025
2185
|
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
2026
2186
|
Read nodeName through the cached prototype getter — a clobbering
|
|
2027
2187
|
child named "nodeName" on the form root would otherwise shadow
|
|
@@ -2031,6 +2191,9 @@ function createDOMPurify() {
|
|
|
2031
2191
|
if (typeof nn === 'string') {
|
|
2032
2192
|
const tagName = transformCaseFunc(nn);
|
|
2033
2193
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
2194
|
+
/* Fail closed on a live root: neutralize handlers/children before
|
|
2195
|
+
throwing, exactly as the mid-walk abort path does. */
|
|
2196
|
+
_neutralizeRoot(dirty);
|
|
2034
2197
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
2035
2198
|
}
|
|
2036
2199
|
}
|
|
@@ -2045,6 +2208,10 @@ function createDOMPurify() {
|
|
|
2045
2208
|
the application unsanitized. Refuse to sanitize such a root
|
|
2046
2209
|
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
2047
2210
|
if (_isClobbered(dirty)) {
|
|
2211
|
+
/* Fail closed on a live clobbered root before throwing.
|
|
2212
|
+
_neutralizeRoot's reads are clobber-safe (cached getters); the
|
|
2213
|
+
form's non-clobbered descendants, e.g. an armed <img>, are scrubbed. */
|
|
2214
|
+
_neutralizeRoot(dirty);
|
|
2048
2215
|
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
2049
2216
|
}
|
|
2050
2217
|
/* Sanitize attached shadow roots before the main iterator runs.
|
|
@@ -2097,7 +2264,8 @@ function createDOMPurify() {
|
|
|
2097
2264
|
_forceRemove(body.firstChild);
|
|
2098
2265
|
}
|
|
2099
2266
|
/* Get node iterator */
|
|
2100
|
-
const
|
|
2267
|
+
const walkRoot = inPlace ? dirty : body;
|
|
2268
|
+
const nodeIterator = _createNodeIterator(walkRoot);
|
|
2101
2269
|
/* Now start iterating over the created document.
|
|
2102
2270
|
The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
|
|
2103
2271
|
engine/custom-element mutation can detach a node mid-walk so
|
|
@@ -2110,7 +2278,7 @@ function createDOMPurify() {
|
|
|
2110
2278
|
try {
|
|
2111
2279
|
while (currentNode = nodeIterator.nextNode()) {
|
|
2112
2280
|
/* Sanitize tags and elements */
|
|
2113
|
-
_sanitizeElements(currentNode);
|
|
2281
|
+
_sanitizeElements(currentNode, walkRoot);
|
|
2114
2282
|
/* Check attributes next */
|
|
2115
2283
|
_sanitizeAttributes(currentNode);
|
|
2116
2284
|
/* Shadow DOM detected, sanitize it.
|
|
@@ -2124,6 +2292,14 @@ function createDOMPurify() {
|
|
|
2124
2292
|
} catch (error) {
|
|
2125
2293
|
if (inPlace) {
|
|
2126
2294
|
_neutralizeRoot(dirty);
|
|
2295
|
+
/* Nodes _forceRemove'd earlier in the aborted walk are already
|
|
2296
|
+
detached from the root, so _neutralizeRoot's subtree pass does not
|
|
2297
|
+
reach them. Defuse them too, mirroring the success-path loop below. */
|
|
2298
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
2299
|
+
if (entry.element) {
|
|
2300
|
+
_neutralizeSubtree(entry.element);
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2127
2303
|
}
|
|
2128
2304
|
throw error;
|
|
2129
2305
|
}
|