Skip to content

✨ feat(sanitize): add DOM-clobbering isolation#568

Merged
gaborbernat merged 2 commits into
tox-dev:mainfrom
gaborbernat:feat/534-dom-clobbering
Jul 7, 2026
Merged

✨ feat(sanitize): add DOM-clobbering isolation#568
gaborbernat merged 2 commits into
tox-dev:mainfrom
gaborbernat:feat/534-dom-clobbering

Conversation

@gaborbernat

Copy link
Copy Markdown
Member

Sanitizing against an allowlist keeps id and name attributes because nothing about them is malformed, so a value an attacker picks to match a built-in property survives the walk. That value then shadows the property through named access: <input name="attributes"> makes form.attributes resolve to the input, <img name="body"> hides document.body, and <a id="location"> can stand in for document.location. 🛡️ DOMPurify hardens against this with SANITIZE_NAMED_PROPS; turbohtml had no equivalent, so a policy that allowed id/name on kept elements left the collision in place.

Policy.isolate_named_props closes the gap by prefixing every kept id and name value with user-content-, moving it out of the property namespace so no value can collide. turbohtml sanitizes a parsed tree with no DOM to probe, so it takes the static SANITIZE_NAMED_PROPS design rather than SANITIZE_DOM's live value in document check: the prefix is unconditional and complete, where a static reimplementation of that check would be a hand-maintained name list that misses whatever property a future engine adds. A value already carrying the prefix is left alone, so re-sanitizing is a fixpoint.

The prefix runs in the existing single C walk, after attribute_filter has had its say, so the value a filter returns is what gets namespaced and no filter can hand back a clobbering value the isolation then fails to prefix. 🔒 The option is off by default and the safety baseline stays unconditional: on* handlers, scripting elements, and javascript: URLs are removed regardless of the policy, so isolation is one more layer rather than the only one.

closes #534

Policy.isolate_named_props prefixes every kept id and name value with
user-content-, DOMPurify's SANITIZE_NAMED_PROPS. It defuses DOM
clobbering: an attacker-set id or name whose value matches a built-in
document or form property shadows that property through named access,
and the allowlist keeps the attribute because it is otherwise ordinary.
Prefixing moves the value out of the property namespace so no value can
collide. An already-prefixed value is left alone, so re-sanitizing is a
fixpoint.

turbohtml sanitizes a parsed tree with no DOM to probe, so it takes the
static SANITIZE_NAMED_PROPS design rather than SANITIZE_DOM's live
value-in-document check. The prefix runs after attribute_filter in the
existing single C walk, below the caller's reach, and the safety
baseline stays unconditional.

closes tox-dev#534
@gaborbernat gaborbernat added the enhancement New feature or request label Jul 7, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 70 untouched benchmarks
🆕 1 new benchmark
⏩ 18 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 test_feature[sanitize-named-props-spec] N/A 12.9 ms N/A

Comparing gaborbernat:feat/534-dom-clobbering (41b1164) with main (934e2d2)

Open in CodSpeed

Footnotes

  1. 18 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@gaborbernat gaborbernat merged commit c4862e9 into tox-dev:main Jul 7, 2026
48 checks passed
@gaborbernat gaborbernat deleted the feat/534-dom-clobbering branch July 10, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sanitizer: DOM-clobbering neutralization (named-property isolation)

1 participant