Outdated HTML Sanitizer Can Be Tricked Into Allowing Malicious Scripts
Your website uses a popular library called DOMPurify to clean up user-submitted content before displaying it — think of it like a filter that removes dangerous code from text people type in. A flaw in older versions of this library means that, under specific conditions, that filter can be bypassed, allowing harmful scripts to slip through and run in your visitors' browsers. The fix is a straightforward library update.
Business Impact And Actions
high urgencyBusiness Impact
If exploited, an attacker could run malicious code in a visitor's browser session — potentially stealing login tokens, impersonating users, or manipulating what they see on your site. This could expose you to data breach liability, damage customer trust, and create compliance issues if you handle personal data. The attack requires two conditions to line up simultaneously, which limits real-world risk, but the fix is quick and low-effort.
What To Do
- Ask your developer to update the DOMPurify library to version 3.3.2 or later — this is typically a 15-minute task.
- If an immediate update isn't possible, ask your developer to temporarily stop using the 'USE_PROFILES' configuration option in DOMPurify as a short-term workaround.
- After the update is deployed, ask your developer to confirm the new version is live using the verification steps in the technical notes.
- If your site allows users to submit or display HTML content (e.g. rich text editors, comments, user profiles), prioritise this fix — those are the areas most at risk.
DOMPurify < 3.3.2 — Prototype Pollution Bypasses USE_PROFILES Attribute Allowlist (GHSA-cj63-jhhr-wcxv)
medium severity CVSS 5.3-6.1Vulnerability Explanation
When DOMPurify is configured with USE_PROFILES (e.g., { USE_PROFILES: { html: true } }), it resets ALLOWED_ATTR to a plain JavaScript array literal ([]) before populating it with the profile's allowlist. Because standard arrays inherit from Array.prototype, any property injected onto Array.prototype via prototype pollution is also visible as a key on ALLOWED_ATTR. DOMPurify validates attributes with a lookup of the form ALLOWED_ATTR[lcName], so a polluted Array.prototype.onclick = true causes the sanitizer to treat 'onclick' as an explicitly allowed attribute — even though it is not in the profile. A proof-of-concept demonstrates that <img onclick=alert(1)> survives sanitization and executes when inserted into the DOM. Exploitation requires a separate prototype pollution primitive to already exist in the same JavaScript runtime, which is the primary constraint on exploitability.
Root Cause
ALLOWED_ATTR is instantiated as a standard array literal ([]) during USE_PROFILES processing rather than a null-prototype object (Object.create(null)). Standard arrays inherit from Array.prototype, making them susceptible to prototype pollution. The fix in 3.3.2 replaces the array literal with a prototype-less object, structurally eliminating the pollution vector.
Technical Impact
An attacker who can achieve prototype pollution in the same JavaScript context can bypass DOMPurify's attribute sanitization entirely when USE_PROFILES is active. This results in DOM-based XSS: event handler attributes such as onclick, onmouseover, onerror, etc. survive sanitization and execute arbitrary JavaScript in the victim's browser session. Consequences include session token theft, credential harvesting, UI redressing, and unauthorized actions performed as the authenticated user.
Severity Justification
CVSS v4.0 base score of 5.3 (Medium) per GHSA-cj63-jhhr-wcxv. CVSS v3.1 vector AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N. Attack complexity is effectively high in practice because a separate prototype pollution primitive must already exist in the runtime — the vulnerability is a chained bypass, not a standalone exploit.
Affected Components
DOMPurify >= 0.0.0 < 3.3.2
Remediation Steps
- Update DOMPurify to version 3.3.2 or later: npm install dompurify@latest (or yarn upgrade dompurify). This replaces the vulnerable array literal with a null-prototype object, permanently eliminating the pollution vector.
- If you cannot update immediately, remove or disable the USE_PROFILES option from your DOMPurify configuration as a temporary workaround. Applications not using USE_PROFILES are not affected by this specific bypass.
- Audit your codebase for any other libraries or patterns that may introduce prototype pollution (e.g., deep merge utilities, query-string parsers, JSON deserialization without schema validation) — these are the prerequisite for this attack chain.
- After deploying the update, run your existing test suite and verify the DOMPurify version in your bundle using the verification steps below.
Verification Steps
- Check the installed version: node -e "console.log(require('dompurify/package.json').version)" — should output 3.3.2 or higher.
- In a browser console, verify: DOMPurify.version — should return '3.3.2' or later.
- Run a quick functional test: DOMPurify.sanitize('<img onclick=alert(1)>', { USE_PROFILES: { html: true } }) — the output should not contain onclick.
- Check your package-lock.json or yarn.lock to confirm no nested dependency is pinning an older version of dompurify.
Code Examples (javascript)
// DOMPurify < 3.3.2 with USE_PROFILES
// If Array.prototype is polluted elsewhere:
Array.prototype.onclick = true;
const clean = DOMPurify.sanitize('<img src=x onclick=alert(1)>', {
USE_PROFILES: { html: true }
});
// clean still contains onclick — XSS survives
document.body.innerHTML = clean; // alert fires
// After upgrading to DOMPurify >= 3.3.2
// ALLOWED_ATTR is now a null-prototype object internally
// Prototype pollution no longer affects attribute lookups
Array.prototype.onclick = true; // has no effect on DOMPurify
const clean = DOMPurify.sanitize('<img src=x onclick=alert(1)>', {
USE_PROFILES: { html: true }
});
// clean = '<img src="x">' — onclick is correctly stripped
Best Practices
- Always use null-prototype objects (Object.create(null)) for security-sensitive lookups and allowlists to prevent prototype pollution from affecting them.
- Audit third-party libraries for prototype pollution vulnerabilities — a pollution primitive in any dependency can chain into sanitizer bypasses like this one.
- Implement a Content Security Policy (CSP) with a strict script-src directive as a defence-in-depth layer; even if a sanitizer is bypassed, a well-configured CSP can block inline event handler execution.
- Pin and regularly update security-critical dependencies like DOMPurify; subscribe to their GitHub security advisories to be notified of future issues.
Found this in your infrastructure?
VulWall scans for this and dozens of other issues automatically.
Scan Your Domain Free