Client-Side Sanitisation
debt(d5/e3/b5/t7)
Closest to 'specialist tool catches it' (d5). ESLint and Semgrep can detect the common pattern of innerHTML with unsanitized user input, but detection requires configuration and these tools may miss sophisticated bypasses or conditional sanitisation logic. Runtime XSS from misconfigured DOMPurify allowlists or server-side sanitisation gaps remain silent until penetration testing or user-reported attacks.
Closest to 'simple parameterised fix' (e3). The quick_fix states 'Use DOMPurify.sanitize(html) before innerHTML' — a one-line addition to the vulnerable code. However, the full remediation is slightly heavier: configuring DOMPurify's allowlist correctly for your use case, and adding complementary server-side sanitisation with HTMLPurifier, touches multiple layers (client and backend). Scoring e3 for the minimum viable fix; e5 would apply if ensuring comprehensive server-side + client-side coverage.
Closest to 'persistent productivity tax' (b5). Client-side sanitisation is not a load-bearing architectural choice (not b7), but it imposes ongoing cognitive load: every rich-text-input, markdown renderer, and dynamic HTML insertion point requires sanitisation logic; developers must remember to sanitise before innerHTML; misconfiguration (allowlist tuning) is per-component; and the false sense of security from DOMPurify-only (misconception) means architectural decisions about where validation happens are repeated across teams. The burden is distributed, not centralised.
Closest to 'serious trap' (t7). The canonical misconception is that 'Client-side DOMPurify is sufficient XSS protection,' which directly contradicts how server-side sanitisation is supposed to work (server-side is the primary defence, client-side is defence-in-depth). This trap contradicts the principle that security boundaries should not trust the client. Additionally, the common mistake 'sanitising after insertion' is a catastrophic logic error that looks correct but completely fails. Score is t7 rather than t9 because the trap is well-documented in security literature; a competent developer who reads the misconception field will learn the right model, though many in the wild still fall for it.
Also Known As
TL;DR
Explanation
When rich HTML must be rendered (user-formatted posts, markdown output), textContent is too restrictive. DOMPurify.sanitize(html) strips script tags, event handlers, and dangerous attributes while keeping safe formatting. The native Sanitizer API (Chrome 105+) provides built-in sanitisation. Trusted Types (Chrome) enforce that only sanitised values reach innerHTML. Server-side sanitisation (PHP's HTMLPurifier) is the primary defence — client-side sanitisation is defence-in-depth.
Common Misconception
Why It Matters
Common Mistakes
- Relying on DOMPurify as the only XSS defence (no server-side sanitisation)
- Not configuring DOMPurify allowlist for your specific allowed tags
- Sanitising after insertion — sanitise before innerHTML, not after
Code Examples
// Raw innerHTML — stored XSS:
el.innerHTML = post.htmlContent; // attacker's script executes
// DOMPurify — safe HTML insertion:
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(post.htmlContent, {
ALLOWED_TAGS: ['b','i','em','strong','a','p','ul','li'],
ALLOWED_ATTR: ['href', 'target'],
});
el.innerHTML = clean;
// Native Sanitizer API (modern browsers):
const sanitizer = new Sanitizer();
el.setHTML(post.htmlContent, { sanitizer });