DOM clobbering lets markup that contains no script at all change what your JavaScript reads, so an application that strips scripts and event handlers from user content can still end up loading code from an attacker's server. It works because browsers expose elements with id and name attributes as named properties of window and document, and those properties can shadow variables and browser APIs that page scripts expect. The risk is not theoretical: in 2024 the build tools webpack, Rollup and Vite each shipped fixes for generated code that a clobbered lookup could redirect. This guide explains the mechanism at the level a defender needs, how to find exposed code, and the layered controls that prevent it. It is written for front-end engineers, application security teams and assessors, and it deliberately contains no working attack markup.
Key takeaways
- Scriptless HTML is not harmless HTML. If user content can carry
idornameattributes, it can interfere with script logic on the same page. - Sanitise the attributes, not just the tags. Configure the sanitiser to remove or namespace
idandnameon untrusted markup; DOMPurify'sSANITIZE_NAMED_PROPSis built for this. - Stop reading globals. Code that reads configuration from
windowordocumentproperties, especially with an||fallback, is the pattern clobbering exploits. - Check types before trusting values. A clobbered value is an
ElementorHTMLCollection, so aninstanceofor type check catches it. - Keep dependencies and bundles current. Generated runtime code from bundlers has been vulnerable, so patching the toolchain and rebuilding is part of the fix.
What DOM clobbering is and why sanitisers miss it
The WHATWG HTML Standard defines "named access on the Window object". The supported property names of a Window include the id of any element in the document, the name of embed, form, img and object elements, and the target names of child browsing contexts such as iframes. When one element matches, the property returns that element; when several match, it returns an HTMLCollection. The standard itself warns that relying on this leads to brittle code, because the set of names can change as features are added, and recommends document.getElementById() or document.querySelector() instead.
OWASP's DOM Clobbering Prevention Cheat Sheet describes the security consequence: named element references take precedence over built-in APIs and developer-defined properties on window and document. If a script reads a global that the developer expected to be undefined, or a property on an object that was expected to hold a configuration value, injected markup can supply an element in its place. Because many elements convert to a string through attributes such as a link's href, the script may then use an attacker-chosen URL as a script source, redirect target or API endpoint.
PortSwigger's Web Security Academy lists the conditions that make it exploitable:
- Controlled markup: the attacker can place HTML on the page but cannot run script directly.
- Permitted attributes: the filter allows
idorname. - A gadget in the code: page JavaScript reads a global or object property that may be undefined, often through an
||fallback. - Unchecked DOM reads in filters: client-side filtering code reads DOM properties without confirming what they are, which can let clobbering bypass the filter itself.
This is why a sanitiser that correctly removes <script>, event handlers and javascript: URLs can still leave a page exposed. The markup it lets through is valid, harmless-looking and scriptless; the vulnerability is in the page's own code. For the sanitiser fundamentals see the HTML Sanitisation Guide.
Where it shows up: real-world gadgets
The most instructive cases are in code developers did not write themselves. In 2024 three widely used JavaScript bundlers published advisories for DOM clobbering in the runtime code they generate. Each required a page that also rendered attacker-influenced, scriptless HTML with unsanitised name or id attributes, for example from comments, forums, markdown renderers or web mail.
| Advisory | Affected code | Condition | Fixed in |
|---|---|---|---|
| CVE-2024-43788 (GHSA-4vvj-4cpr-p986), webpack | AutoPublicPathRuntimeModule, which works out the base URL for loading further chunks | output.publicPath unset or "auto"; the runtime's lookup of the current script could be shadowed, so later chunks load from an attacker-chosen base path. The advisory reports a real-world instance in Canvas LMS | 5.94.0 |
| CVE-2024-47068 (GHSA-gcx4-mw62-g8wm), Rollup | import.meta handling that resolves URLs from document.currentScript | cjs, umd or iife output using import.meta or plugin-emitted assets | 2.79.2, 3.29.5, 4.22.4 |
| CVE-2024-45812 (GHSA-64vr-g452-qvp3), Vite | Asset URL resolution relative to document.currentScript | cjs, iife or umd output that loads assets by relative path | 5.4.6, 5.3.6, 5.2.14, 5.1.8, 4.5.5, 3.2.11 |
All three were rated Moderate (CVSS 3.1 base score 6.4) and classified as CWE-79. The lesson for defenders is that the vulnerable lookup sat in build output, not application source, so a code review of your own files would not find it. Software composition analysis, prompt toolchain updates and a rebuild of affected bundles are required; see the Dependency Update Automation Guide.
Typical gadget patterns in application code, described here so reviewers can recognise them:
- Global configuration with a fallback: code that uses a global configuration object if it exists and a default otherwise.
- Feature detection by property presence: code that checks whether a browser API or library global exists before using it; a clobbered name makes the check pass with an element.
documentAPI shadowing: code that calls adocumentmethod or reads adocumentproperty after untrusted markup has been inserted; a named form or image can replace it.- Form property shadowing: scripts that read a form's
action,attributesorsubmitwhen the form contains user-controlled inputs whose names collide. - Client-side sanitisers that walk the DOM: filters that read
attributesorchildNodeswithout confirming the value is aNamedNodeMaporNodeList.
Prevention controls
No single control is sufficient. The table maps each defence to what it stops and where it falls short, drawing on the OWASP cheat sheet, PortSwigger guidance and the DOMPurify documentation.
| Control | What it stops | Limits |
|---|---|---|
Sanitiser removes or namespaces id and name | Untrusted markup creating named properties at all | Breaks in-page anchors and form names in user content unless namespaced; only covers markup that passes through the sanitiser |
DOMPurify SANITIZE_DOM (on by default) | Collisions with built-in DOM APIs and properties | Does not protect developer-defined globals |
DOMPurify SANITIZE_NAMED_PROPS: true (off by default) | Collisions with developer-defined variables, by prefixing id and name values with user-content- | Links and CSS that target the original id must be updated |
HTML Sanitizer API with removeAttributes for id and name | Same as above, natively in supporting browsers via setHTML() | Limited browser availability: fall back to DOMPurify, never to unsanitised insertion |
Explicit declarations (const, let), strict mode, local scope, encapsulation | Implicit globals that clobbering can supply | Code that deliberately reads window.X remains exposed |
Type checks (instanceof, checking for DOM nodes) before sensitive use | An element or collection being used as a string, URL or object | Must be applied at every sensitive read |
Strict Content Security Policy script-src | Clobbered script URLs loading code from origins outside the allow list | Does not stop clobbered values reaching existing sinks such as eval() or same-origin script paths |
| Trusted Types | Clobbered strings reaching DOM XSS injection sinks without passing a policy | Requires policy design and browser support; does not change what the code reads |
Object.freeze() on app-owned configuration held in local scope | Later modification of trusted configuration objects | Shallow; never freeze window, document or DOM elements (OWASP notes freezing window throws) |
Sanitiser configuration: where user content does not need id or name, remove them. Where it does, for example heading anchors in rendered markdown, namespace them with DOMPurify's SANITIZE_NAMED_PROPS option and update the links that target them. Keep SANITIZE_DOM on; the DOMPurify README marks disabling it as a minor XSS risk. With the HTML Sanitizer API, build a Sanitizer that removes the two attributes and pass it to setHTML(). MDN currently marks the API as limited availability, so feature-detect it and keep DOMPurify as the fallback.
Code patterns: read configuration from module scope or an explicitly passed object, not from window. Replace "global or default" fallbacks with explicit initialisation. Use document.getElementById() and querySelector() rather than named access, as the HTML Standard advises. Before a value is used as a URL, script source or HTML, confirm its type. In client-side filters, confirm that attributes is a NamedNodeMap before iterating it.
Browser policy layers: a nonce- or hash-based CSP limits where a clobbered script URL can load from, and Trusted Types forces strings through a reviewed policy before they reach injection sinks. See the Content Security Policy Guide and the Trusted Types and DOM XSS Prevention Guide.
Choosing controls by application type
| Application | Exposure | Priority controls |
|---|---|---|
| Renders rich user content (comments, wikis, web mail, markdown, support tickets) | High: untrusted markup sits beside application scripts | Namespace or strip id and name; bundler patches; strict CSP; type checks in shared code |
| Embeds third-party widgets or scripts that inject HTML | Medium: third-party HTML injection can supply clobbering markup, as the bundler advisories note | Inventory third-party scripts; CSP; isolate widgets in sandboxed iframes where possible |
| Single-page app with framework templating and no raw HTML insertion | Lower: frameworks escape by default | Audit every raw HTML escape hatch; keep bundlers patched; avoid globals for configuration |
| Static marketing site with no user content | Low | Keep dependencies current; CSP as baseline |
A small team with one product can usually remove id and name from user content outright and patch its bundler in a sprint. A large organisation with many front ends should centralise sanitiser configuration in a shared library, so the decision is made once and enforced by tests, and track bundler versions across repositories through software composition analysis.
Implementation: a phased approach
- Find the inputs. List every feature that renders user-supplied or third-party HTML: rich text editors, markdown, email rendering, imported documents and widgets. These are the pages where clobbering markup can appear.
- Patch the toolchain. Check webpack, Rollup and Vite versions against the advisories above in every repository, upgrade, rebuild and redeploy. Where feasible, set webpack's
output.publicPathexplicitly, as the webpack advisory recommends. - Tighten the sanitiser. Change the shared sanitiser configuration to remove or namespace
idandname. Run it first against a corpus of real stored content in a test environment to find broken anchors and styles before release. - Fix gadgets in your own code. Search for reads of
window.anddocument.properties used as configuration,||fallbacks on globals, and named access. Replace them with explicit declarations, local scope and type checks. - Add browser policy layers. Deploy CSP and Trusted Types in report-only mode, review violations, then enforce.
- Test and keep testing. Add regression tests that render untrusted content containing named attributes and assert the sanitiser output and the application's behaviour. Include clobbering in assessment scope for features that render user HTML.
- Roll back safely. Ship sanitiser changes behind a configuration flag so a rendering regression can be reverted without reopening the old setting everywhere; never roll back a bundler security patch to fix a build issue.
Detection and measurement
- Stored content scans: periodically scan stored user content for
idandnamevalues that match known global names, browser API names or names used in your scripts. Matches show attempted or accidental collisions. - CSP and Trusted Types reports: violation reports for unexpected script origins or blocked sink assignments on pages with user content can indicate a clobbered URL being used.
- Static analysis: lint for implicit globals and configure code review rules for global reads with fallbacks and for named access on
windowanddocument. See the AI-Assisted Code Review and AI SAST Guide. - Dependency monitoring: software composition analysis alerts for bundler and sanitiser advisories, with a time-to-patch target.
Metrics to report:
- Percentage of user-content rendering paths using the shared sanitiser with named attribute protection.
- Repositories on patched bundler versions, against the total in scope.
- Number of open gadget findings (global reads with fallbacks, unchecked DOM reads) in front-end code.
- Pages with user content covered by an enforced CSP and Trusted Types policy.
- Median time from a published client-side library advisory to deployed fix.
Common failure patterns
- Allowing
idandnamebecause they look inert. Sanitiser allow lists often keep them for anchors and forms without considering named access. - Turning off
SANITIZE_DOMto fix a rendering issue. This removes the built-in collision protection for the whole application. - Patching the library but not rebuilding. The bundler fixes only take effect when affected bundles are regenerated and redeployed.
- Relying on CSP alone. CSP limits new script origins but does not stop a clobbered value reaching an existing sink.
- Freezing or overriding
windowproperties as a fix. It throws or breaks code, and named access still takes precedence on undefined names. - Feature detection by property existence. A clobbered name makes an unsupported API look present.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| OWASP ASVS 5.0 | V3.2.3 (Level 3) | Requires avoiding DOM clobbering through explicit variable declarations, strict type checking, not storing globals on the document object and namespace isolation |
| OWASP ASVS 5.0 | V1.3.1 (Level 1) | Untrusted HTML from WYSIWYG editors or similar sanitised with a well-known library |
| OWASP ASVS 5.0 | V3.4.3 (Level 2) | Content-Security-Policy with object-src 'none', base-uri 'none' and an allow list, nonces or hashes |
| CWE | CWE-79 Improper Neutralization of Input During Web Page Generation | The classification used in the webpack, Rollup and Vite advisories |
| NIST SP 800-53 Rev 5 | SI-10 Information Input Validation; SI-15 Information Output Filtering; SA-11 Developer Testing and Evaluation | Sanitiser configuration, output handling and regression testing |
| MITRE ATT&CK v20 | T1189 Drive-by Compromise; T1059.007 JavaScript | Script loaded through a clobbered URL runs in visitors' browsers |
Practitioner checklist
- List every feature that renders user-supplied or third-party HTML.
- Upgrade webpack, Rollup and Vite past the 2024 advisories, then rebuild and redeploy.
- Configure the shared sanitiser to remove or namespace
idandname; keep DOMPurify'sSANITIZE_DOMon. - Use the HTML Sanitizer API only with feature detection and a DOMPurify fallback.
- Replace global configuration reads and
||fallbacks with explicit, locally scoped declarations. - Add type checks before values are used as URLs, script sources or HTML.
- Enforce a nonce- or hash-based CSP and Trusted Types on pages with user content.
- Add regression tests and include clobbering in assessment scope for user-content features.
Standards and references
- OWASP: DOM Clobbering Prevention Cheat Sheet
- WHATWG: HTML Standard, Named access on the Window object
- PortSwigger Web Security Academy: DOM clobbering
- Cure53: DOMPurify
- MDN: HTML Sanitizer API
- webpack: GHSA-4vvj-4cpr-p986 (CVE-2024-43788)
- Rollup: GHSA-gcx4-mw62-g8wm (CVE-2024-47068)
- Vite: GHSA-64vr-g452-qvp3 (CVE-2024-45812)
Related NHI Mgmt Group resources: HTML Sanitisation Guide · Client-Side Security Guide · Web Application Attacks Guide · Cross-Site Scripting (XSS) Prevention Guide