Summary
A configuration handling flaw in DOMPurify before 3.4.11 lets an uponSanitizeAttribute hook permanently modify the shared ALLOWED_ATTR allowlist instead of working on a safe clone.
If an attacker can influence or register such a hook and later submit untrusted HTML, previously disallowed attributes like onerror may be treated as safe, enabling stored cross-site scripting in otherwise sanitized content.
A configuration handling flaw in DOMPurify before 3.4.11 lets an uponSanitizeAttribute hook permanently modify the shared ALLOWED_ATTR allowlist instead of working on a safe clone.
If an attacker can influence or register such a hook and later submit untrusted HTML, previously disallowed attributes like onerror may be treated as safe, enabling stored cross-site scripting in otherwise sanitized content.
Affected context
This vulnerability affects applications that:
- Use DOMPurify versions before 3.4.11 to sanitize HTML or rich-text user input
- Rely on
setConfig()together with anuponSanitizeAttributehook - Treat DOMPurify as a strong security boundary against dangerous attributes such as event handlers
- Potentially reuse a DOMPurify instance or configuration across different trust levels (e.g., admin vs. regular users)
In these contexts, a hook configured for "trusted" content can inadvertently weaken sanitization for later "untrusted" content because the shared allowlist is mutated.
This vulnerability affects applications that:
- Use DOMPurify versions before 3.4.11 to sanitize HTML or rich-text user input
- Rely on
setConfig()together with anuponSanitizeAttributehook - Treat DOMPurify as a strong security boundary against dangerous attributes such as event handlers
- Potentially reuse a DOMPurify instance or configuration across different trust levels (e.g., admin vs. regular users)
In these contexts, a hook configured for "trusted" content can inadvertently weaken sanitization for later "untrusted" content because the shared allowlist is mutated.
Exploit preconditions
- Attack surface: HTML or rich-text input that is sanitized by DOMPurify and then rendered in a browser context.
- Required access: Ability to supply content that is sanitized with a configuration polluted by a malicious or overly permissive
uponSanitizeAttributehook. In some designs, this may mean: - Direct control over hook registration (e.g., a plugin system or misconfigured multi-tenant feature), or
- Indirect influence where a high-privilege user configures a custom hook for some content, and the same DOMPurify instance is reused for untrusted content.
- Trigger point: Sanitization of attacker-controlled HTML after the allowlist has been modified, causing attributes like
onerrorto pass through. - Why exploitation is possible: DOMPurify fails to clone the
ALLOWED_ATTRallowlist whensetConfig()is used withuponSanitizeAttribute, so changes made by a particular hook persist globally and affect subsequent sanitization calls that assume a stricter default configuration.
- Attack surface: HTML or rich-text input that is sanitized by DOMPurify and then rendered in a browser context.
- Required access: Ability to supply content that is sanitized with a configuration polluted by a malicious or overly permissive
uponSanitizeAttributehook. In some designs, this may mean: - Direct control over hook registration (e.g., a plugin system or misconfigured multi-tenant feature), or
- Indirect influence where a high-privilege user configures a custom hook for some content, and the same DOMPurify instance is reused for untrusted content.
- Trigger point: Sanitization of attacker-controlled HTML after the allowlist has been modified, causing attributes like
onerrorto pass through. - Why exploitation is possible: DOMPurify fails to clone the
ALLOWED_ATTRallowlist whensetConfig()is used withuponSanitizeAttribute, so changes made by a particular hook persist globally and affect subsequent sanitization calls that assume a stricter default configuration.
Why this happens
The core issue is a configuration state-sharing bug that breaks the assumption that per-use configuration is isolated.
DOMPurify maintains an internal ALLOWED_ATTR allowlist of safe attributes. When setConfig() is called with an uponSanitizeAttribute hook, DOMPurify should work on a cloned copy of this allowlist per configuration. Instead, versions before 3.4.11 let hook logic mutate the shared allowlist directly.
This maps to CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting) because user-controlled HTML ends up being rendered with dangerous attributes left intact. Conceptually, it is a combination of:
- Insecure global state that controls sanitization behavior, and
- Misapplied trust boundaries between "trusted" and "untrusted" sanitization configurations.
The core issue is a configuration state-sharing bug that breaks the assumption that per-use configuration is isolated.
DOMPurify maintains an internal ALLOWED_ATTR allowlist of safe attributes. When setConfig() is called with an uponSanitizeAttribute hook, DOMPurify should work on a cloned copy of this allowlist per configuration. Instead, versions before 3.4.11 let hook logic mutate the shared allowlist directly.
This maps to CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting) because user-controlled HTML ends up being rendered with dangerous attributes left intact. Conceptually, it is a combination of:
- Insecure global state that controls sanitization behavior, and
- Misapplied trust boundaries between "trusted" and "untrusted" sanitization configurations.
Potential impact
- Stored XSS where attacker-supplied content can execute arbitrary JavaScript in victims' browsers
- Session hijacking via theft of cookies or authentication tokens (if not otherwise protected)
- Account takeover through malicious actions executed in the victim's context
- Defacement or injection of misleading UI, phishing overlays, or malicious redirects
- Exfiltration of sensitive data accessible to the victim's browser session
- Stored XSS where attacker-supplied content can execute arbitrary JavaScript in victims' browsers
- Session hijacking via theft of cookies or authentication tokens (if not otherwise protected)
- Account takeover through malicious actions executed in the victim's context
- Defacement or injection of misleading UI, phishing overlays, or malicious redirects
- Exfiltration of sensitive data accessible to the victim's browser session
General mitigation guidance
Effective mitigation focuses on isolating sanitization configuration and minimizing mutable shared state:
- Use DOMPurify in a way that treats configuration as immutable for untrusted content; avoid dynamic hooks that selectively allow dangerous attributes.
- Prefer separate, clearly scoped DOMPurify instances (or configurations) for different trust levels instead of reusing a single global instance.
- Avoid conditionally allowing event-handler attributes (
on*) orjavascript:URLs in any context that may touch untrusted content. - Treat hooks as part of the security boundary: implement them defensively, assuming they might be invoked with fully untrusted input.
- Apply defense-in-depth by combining sanitization with contextual output encoding (e.g., HTML attribute/JS string encoding) in templates.
- Enforce least privilege in client-side logic so that even if some XSS occurs, the impact is reduced (e.g., avoid exposing powerful APIs to the DOM unnecessarily).
Effective mitigation focuses on isolating sanitization configuration and minimizing mutable shared state:
- Use DOMPurify in a way that treats configuration as immutable for untrusted content; avoid dynamic hooks that selectively allow dangerous attributes.
- Prefer separate, clearly scoped DOMPurify instances (or configurations) for different trust levels instead of reusing a single global instance.
- Avoid conditionally allowing event-handler attributes (
on*) orjavascript:URLs in any context that may touch untrusted content. - Treat hooks as part of the security boundary: implement them defensively, assuming they might be invoked with fully untrusted input.
- Apply defense-in-depth by combining sanitization with contextual output encoding (e.g., HTML attribute/JS string encoding) in templates.
- Enforce least privilege in client-side logic so that even if some XSS occurs, the impact is reduced (e.g., avoid exposing powerful APIs to the DOM unnecessarily).
How teams detect issues like this
Detection can combine code-level analysis with configuration review:
- Static analysis / linting:
- Flag uses of DOMPurify
setConfig()that defineuponSanitizeAttributehooks, especially those that modify allowlists or treat attributes differently based on conditions. - Identify global or singleton DOMPurify instances reused across modules with different trust assumptions.
- Code review patterns:
- Review hook implementations for any logic that whitelists event handlers (
onload,onerror, etc.),stylewith dangerous content, orjavascript:URLs. - Look for places where a configuration intended for "trusted" content sanitization can be invoked for general user input.
- Automated testing:
- Add regression tests that attempt to pass through known dangerous attributes and protocols, verifying they are stripped for all untrusted content paths.
- Use security scanners or browser-based test harnesses to inject test payloads and check if DOMPurify-sanitized content still executes scripts.
- Policy enforcement:
- Establish internal guidelines that restrict custom hooks in sanitization libraries and require security review for any exceptions.
Detection can combine code-level analysis with configuration review:
- Static analysis / linting:
- Flag uses of DOMPurify
setConfig()that defineuponSanitizeAttributehooks, especially those that modify allowlists or treat attributes differently based on conditions. - Identify global or singleton DOMPurify instances reused across modules with different trust assumptions.
- Code review patterns:
- Review hook implementations for any logic that whitelists event handlers (
onload,onerror, etc.),stylewith dangerous content, orjavascript:URLs. - Look for places where a configuration intended for "trusted" content sanitization can be invoked for general user input.
- Automated testing:
- Add regression tests that attempt to pass through known dangerous attributes and protocols, verifying they are stripped for all untrusted content paths.
- Use security scanners or browser-based test harnesses to inject test payloads and check if DOMPurify-sanitized content still executes scripts.
- Policy enforcement:
- Establish internal guidelines that restrict custom hooks in sanitization libraries and require security review for any exceptions.

