✅ test(clean): validate sanitizer against DOMPurify suite#583
Merged
gaborbernat merged 3 commits intoJul 7, 2026
Merged
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
e2d0307 to
a7412d2
Compare
Conformance suites and the bug fixes they surface carry no changelog; the feature's own fragment covers the capability.
a7412d2 to
69e22ea
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The sanitizer's shipped DOMPurify-parity features (
SAFE_FOR_TEMPLATES,SANITIZE_NAMED_PROPS,CUSTOM_ELEMENT_HANDLING,USE_PROFILES) were covered only by turbohtml's own hand-written expectations. Green coverage is not the same as spec-correctness, so this validates them against the authority: DOMPurify's own test corpus and a live DOMPurify build.DOMPurify is vendored as a pinned shallow submodule at
tests/conformance/DOMPurify, andtests/conformance/test_sanitizer_dompurify_conformance.pyruns every payload in itstest/fixtures/expect.mjs(~219 XSS vectors and config cases) throughturbohtml.clean.sanitize. 🔐 The oracle is DOMPurify's actual output: a batch Node runner (tools/bench/node/dompurify_conformance_runner.js, reusing the existingisomorphic-dompurifydependency) sanitizes the same inputs under the config that mirrors each Policy feature, so the comparison is head-to-head rather than against a frozen string. The absolute security invariant is asserted hard over the whole corpus under every config: turbohtml's output reparses to an inert tree, and it never keeps an executable construct DOMPurify strips. Because DOMPurify keeps nearly all of HTML by default while aPolicyis an explicit allowlist, byte-parity over arbitrary payloads is dominated by allowlist scope rather than the feature under test, so each feature is also validated for exact canonical parity on curated inputs whose tags both allowlists share, where the only variable is the config flag.Two divergences are documented and
xfailed rather than hidden, both cases where turbohtml is stricter and so never less safe: the template stripper collapses only marker runs that can open a template evaluation ({{/${/<%), leaving a bare unmatched close delimiter as inert text, while DOMPurify's greedy regex also eats the text before it; and stripping a disallowed HTML wrapper drops a nested foreign (SVG/MathML) subtree instead of hoisting it. ✨ Nosanitize.cchange was needed: the corpus found no case where turbohtml is less safe or diverges on a feature it claims to support. The harness carries no coverage (it is inrun.omit, like the other differential suites) and skips cleanly when the submodule or Node build is absent.validates #527 #531 #533 #534 #536