basecamp/trix · error · TypeError
root node is clobbered and cannot be sanitized in-place
Error message
root node is clobbered and cannot be sanitized in-place
What it means
Before walking the tree in in-place mode, DOMPurify runs _isClobbered on the root node to detect DOM clobbering attacks (properties like node.name/attributes overwritten by named elements/ids, or broken form associations). A clobbered root cannot be trusted, and because in-place mode cannot detach or replace the root, DOMPurify neutralizes it (using clobber-safe cached getters) and throws this TypeError, fail-closed (see GHSA-r47g-fvhr-h676).
Source
Thrown at action_text-trix/app/assets/javascripts/trix.js:4037
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
}
}
/* Pre-flight the root through _isClobbered. The iterator-driven
removal path can not detach a parent-less root: _forceRemove
falls through to Element.prototype.remove(), which per spec
is a no-op on a node with no parent. A clobbered root would
then survive the main loop with its attributes uninspected,
because _sanitizeAttributes early-returns on _isClobbered. The
result would be an attacker-controlled form, complete with any
event-handler attributes the caller passed in, handed back to
the application unsanitized. Refuse to sanitize such a root
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
if (_isClobbered(dirty)) {
/* Fail closed on a live clobbered root before throwing.
_neutralizeRoot's reads are clobber-safe (cached getters); the
form's non-clobbered descendants, e.g. an armed <img>, are scrubbed. */
_neutralizeRoot(dirty);
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
}
/* Sanitize attached shadow roots before the main iterator runs.
The iterator does not descend into shadow trees. Same fail-closed
barrier as the main walk (campaign-3 F2): a custom-element reaction
inside a shadow root could abort this pre-pass before the walk runs,
which would otherwise leave the entire live tree unsanitized. */
try {
_sanitizeAttachedShadowRoots(dirty);
} catch (error) {
_neutralizeRoot(dirty);
throw error;
}
} else if (_isNode(dirty)) {
/* If dirty is a DOM element, append to an empty document to avoid
elements being stripped by the parser */
body = _initDocument('<!---->');
importedNode = body.ownerDocument.importNode(dirty, true);
if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {View on GitHub (pinned to 4700401311)
Solutions
- Remove or rename the clobbering name/id attributes on the root element before sanitizing (rename to non-colliding values).
- Avoid in-place mode for clobber-prone content: serialize with outerHTML and sanitize the string so DOMPurify can rebuild a clean tree.
- Strip name/id attributes via a SANITIZE hook or preprocessing (e.g. ALLOW_DATA_ATTR off, FORBID_ATTR: ['name']) for untrusted content.
- Catch the TypeError and treat the root as untrusted content: discard it or re-create it from sanitized markup.
Example fix
// before: clobbered root, e.g. <form name="attributes">...
DOMPurify.sanitize(formEl, { IN_PLACE: true }); // throws
// after
formEl.removeAttribute('name'); // de-clobber first
DOMPurify.sanitize(formEl, { IN_PLACE: true });
// or rebuild from string
const safe = DOMPurify.sanitize(formEl.outerHTML); Defensive patterns
Strategy: validation
Validate before calling
function looksClobbered(el) {
return (el.attributes && el.attributes.length) !== el.attributes.length ||
el.querySelector && !!el.querySelector('[name=attributes],[name=nodeType],[name=tagName]');
}
if (root.hasAttribute && root.hasAttribute('name')) root.removeAttribute('name'); Type guard
const isClobberSafeRoot = (n) => n instanceof Element && !n.hasAttribute('name') && !/^(attributes|nodeType|tagName|parentNode)$/.test(n.id || ''); Try / catch
try {
DOMPurify.sanitize(root, { IN_PLACE: true });
} catch (e) {
if (String(e.message).includes('root node is clobbered')) {
const safe = DOMPurify.sanitize(root.outerHTML); // fail closed, rebuild
} else { throw e; }
} Prevention
- Strip name/id attributes from untrusted roots before in-place sanitization.
- Never sanitize legacy markup with named forms/images in IN_PLACE mode; rebuild from strings.
- Audit user content for DOM-clobbering patterns (name/id shadowing document properties).
- Prefer RETURN_DOM/string sanitization over IN_PLACE for untrusted trees.
When it happens
Trigger: Calling DOMPurify.sanitize(rootElement, { IN_PLACE: true }) where the root element itself is clobbered — e.g. a <form> or <img> with a name/id that shadows document properties, or whose named descendants (form, image, attributes collections) collide with node internals.
Common situations: Sanitizing legacy markup with named forms/inputs (<form name="attributes">, <img name="nodeType">); user-generated content embedded with name/id attributes chosen to shadow DOM APIs; migrating legacy pages into in-place sanitization.
Related errors
- a node selected for removal could not be detached from its t
- root node is forbidden and cannot be sanitized in-place
- TrustedTypes policy ' + policyName + ' could not be created.
- A configured TRUSTED_TYPES_POLICY callback (createHTML or cr
- TRUSTED_TYPES_POLICY configuration option must provide a "cr
AI-assisted analysis of basecamp/trix@4700401311 (2026-09-02).
Data as JSON: /api/errors/f41606e238f74e05.
Report an issue: GitHub.