Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 18 additions & 19 deletions dist/purify.cjs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/purify.cjs.js.map

Large diffs are not rendered by default.

37 changes: 18 additions & 19 deletions dist/purify.es.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,7 @@ function createDOMPurify() {
* @param tagName the node's transformCaseFunc'd tag name
* @return true if the node was removed, false if kept
*/
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
/* Check if we have a custom element to handle */
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
Expand All @@ -1603,27 +1603,26 @@ function createDOMPurify() {
const childNodes = getChildNodes(currentNode);
if (childNodes && parentNode) {
const childCount = childNodes.length;
/* In-place: hoist the *original* children so the iterator visits
and sanitises them through the same allowlist pass as every other
node. The caller built the tree in the live document, so the
originals carry already-queued resource events (`<img onerror>`,
`<video>`/`<audio>` error, lazy/`onload`, …); cloning would leave
those originals detached but still armed, firing in page scope
while the returned tree looked clean. Moving is safe in-place: the
root is pre-validated as an allowed tag and so is never the node
being removed, which keeps `parentNode` inside the iterator root
and the relocated child inside the serialised tree.
Otherwise (string / DOM-copy paths): clone. The iterator is rooted
at — and the result serialised from — `body`, so a restrictive
ALLOWED_TAGS that removes `body` itself must leave its content in
place, which only cloning does; and those paths parse into an
inert document, so their discarded originals never had a queued
event to neutralise.
/* Hoist by moving each child up one level rather than deep-cloning
it. Moving transfers every descendant exactly once, so a chain of
nested disallowed elements costs O(n) instead of the O(n^2) that
re-cloning the shrinking subtree at each level produced; it also
empties the removed original, so `DOMPurify.removed` no longer
pins whole subtrees. Moving preserves the in-place guarantee too:
an original carrying already-queued resource events (`<img
onerror>`, `<video>`/`<audio>` error, lazy/`onload`, …) is
relocated and sanitised rather than left detached but still armed.
The sole case that must clone is removing the walk root itself.
The result is serialised from the root's subtree, so a restrictive
ALLOWED_TAGS that strips the root (`body` on the string path) must
leave the content inside it, which only cloning does. In IN_PLACE
the root is pre-validated as an allowed tag and so is never removed
here, so that path always takes the move branch.
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
valid whether we move (drops the trailing entry) or clone (leaves
the list intact). */
for (let i = childCount - 1; i >= 0; --i) {
const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
const hoisted = currentNode === root ? cloneNode(childNodes[i], true) : childNodes[i];
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
}
}
Expand Down Expand Up @@ -1686,7 +1685,7 @@ function createDOMPurify() {
}
/* Remove element if anything forbids its presence */
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
const removed = _sanitizeDisallowedNode(currentNode, tagName);
const removed = _sanitizeDisallowedNode(currentNode, tagName, root);
/* A false return means the node is a custom element kept via
CUSTOM_ELEMENT_HANDLING - the only keep path through
_sanitizeDisallowedNode. Run afterSanitizeElements on it so the
Expand Down
2 changes: 1 addition & 1 deletion dist/purify.es.mjs.map

Large diffs are not rendered by default.

37 changes: 18 additions & 19 deletions dist/purify.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/purify.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/purify.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/purify.min.js.map

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions osv-scanner.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@
# The legacy-browser harness pins 1.30-1.50.1 on purpose and keeps its own
# suppression at test/legacy-playwright/osv-scanner.toml.
#
# 2026-07: the two entries below are STOPGAPS, not verdicts. Both advisories
# already have published fixes that the current dependency ranges accept,
# and `npm update js-yaml brace-expansion` moves the tree to
# 1.1.16 / 2.1.2 / 5.0.7 and 4.3.0 with the build and jsdom suite green.
# Per the policy above, drop both entries once that lockfile refresh lands
# rather than carrying them to the expiry date.
# 2026-07: the first two entries below are STOPGAPS. Both advisories have
# published fixes the current ranges accept, and `npm update js-yaml
# brace-expansion` moves the tree to 1.1.16 / 2.1.2 / 5.0.8 and 4.3.0 with the
# build and jsdom suite green. Per the policy above, drop those two once that
# lockfile refresh lands rather than carrying them to the expiry date.
#
# The third entry (GHSA-mh99-v99m-4gvg) is different and is NOT cleared by a
# refresh: its only fix is on the 5.x line (5.0.8). The top-level
# brace-expansion updates to 5.0.8 and clears, but the transitive 1.x / 2.x
# copies pulled in by eslint / typescript-estree / test-exclude top out at
# 1.1.16 / 2.1.2 - both below 5.0.8, with no in-line backport - so they stay
# flagged until those parents adopt brace-expansion 5.x (or the copies are
# deduped out). Drop this entry then, not on a version bump of the dep alone.

[[IgnoredVulns]]
id = "GHSA-3jxr-9vmj-r5cp"
Expand All @@ -35,3 +42,8 @@ reason = "brace-expansion (CVE-2026-13149): exponential-time expansion of consec
id = "GHSA-52cp-r559-cp3m"
ignoreUntil = 2027-07-22
reason = "js-yaml (CVE-2026-59869): quadratic CPU time on a chain of mappings using merge keys, availability-only. Dev-only path - pulled in by eslint / cosmiconfig / nyc config loading at 4.2.0 (package.json already pins an override of ^4.2.0, which admits the fix). Only repo-owned config files are parsed, never untrusted YAML, and it is absent from the published artifact. Fixed in 4.3.0, and in 3.15.0 on the 3.x line."

[[IgnoredVulns]]
id = "GHSA-mh99-v99m-4gvg"
ignoreUntil = 2027-07-22
reason = "brace-expansion (CVE-2026-14257): unbounded expansion length causing an out-of-memory process crash, availability-only (CVSS 7.5). OSV range is [0, 5.0.8) with the fix on the 5.x line only. Dev-only path - the top-level copy updates to 5.0.8 and clears, but the transitive 1.1.16 / 2.1.2 copies (eslint / typescript-estree / test-exclude) have no in-line backport and stay below 5.0.8 until those parents adopt brace-expansion 5.x. DOMPurify ships no runtime dependencies, so this is absent from the published artifact, and CI only ever expands repo-owned glob patterns, never attacker-controlled input."
Loading