HTML Tutorial 0/139 lessons ~6 min read Lesson 122

    HTML Sanitization

    html sanitization html sanitization applies owasp allowlist policies via maintained libraries like html sanitization allowlists safe tags and attributes when

    Course progress0%
    Focus
    18 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    HTML sanitization allowlists safe tags and attributes when user or CMS content must render as HTML — WYSIWYG articles, comments with formatting, email previews. OWASP recommends established libraries (DOMPurify) over regex strip_tags; staff engineers define allowlist policy per content type and validate in CI.

    Business problem

    Rich text CMS fields are stored XSS reservoirs — authors paste from Word with embedded scripts; users upload SVG with onload. Regex sanitizers miss nested parser tricks (mXSS). One bypass compromises every reader.

    • Policy drift: Marketing adds iframe allow for embeds — opens XSS if src unchecked.
    • Server vs client: Sanitize server-side at save and at render — client-only bypassable.

    Why this feature exists

    Browsers parse HTML error-tolerantly — attacker strings exploit parser differential between sanitizer and browser. Allowlist sanitizers walk DOM and strip disallowed nodes/attrs — safer than denylist regex.

    • DOMPurify: Industry standard browser + Node — maintained mXSS fixes.
    • Trusted Types: Sanitizer returns TrustedHTML in supported browsers.

    Browser internals

    Parser differential attacks — sanitizer outputs HTML browser re-parses differently creating script. Modern DOMPurify hooks address mXSS; keep library updated. SVG and MathML namespaces high risk.

    • ALLOWED_TAGS config: Explicit list — default deny.
    • ALLOWED_ATTR: Block event handlers, formaction, xlink:href javascript.

    Rendering workflow

    Sanitize pipeline: Input → server DOMPurify with profile → store clean HTML → render with CSP → optional client re-sanitize defense in depth → monitor CSP violations for bypass attempts.

    • Profiles: USE_PROFILES html vs svg vs math — separate content types.
    • URL attrs: ALLOWED_URI_REGEXP http/https/mailto only.

    Feature deep dive

    DOMPurify allowlist example for comment HTML:

    • Tags: p, br, strong, em, a, ul, ol, li, code — no script, style, iframe.
    • Attrs on a: href, title — rel=noopener noreferrer added by hook.
    • Forbidden: on*, style, srcdoc, data-* unless explicitly needed.
    html
    import DOMPurify from "isomorphic-dompurify";
    const clean = DOMPurify.sanitize(dirtyHtml, {
    ALLOWED_TAGS: ["p", "br", "strong", "em", "a", "ul", "ol", "li", "code"],
    ALLOWED_ATTR: ["href", "title"],
    ALLOW_DATA_ATTR: false,
    ADD_ATTR: ["target"], // then hook sets rel=noopener
    });
    DOMPurify.addHook("afterSanitizeAttributes", (node) => {
    if (node.tagName === "A") {
    node.setAttribute("rel", "noopener noreferrer");
    const href = node.getAttribute("href") || "";
    if (!/^https?:\/\//i.test(href)) node.removeAttribute("href");
    }
    });

    Accessibility analysis

    Sanitizer must preserve semantic tags allowed in policy — stripping headings hurts structure. Don't allow style attr — breaks contrast; use class allowlist tied to design system accessible styles.

    • Alt on img: If allowing img in rich text, require alt attribute in CMS validation.

    SEO impact

    User-generated HTML with spam links hurts site reputation — sanitize href domains or add rel=ugc nofollow on UGC links per Google link spam policies.

    • rel=ugc: Sanitizer hook adds to user comment links.

    Security considerations

    OWASP XSS Prevention — sanitization is last line when HTML required. Never use regex alone. Pen-test sanitizer config changes. CSP still required — sanitizer bypass history exists.

    • PDF/SVG upload: Separate pipeline — not same allowlist as comments.

    Performance impact

    Sanitize at save time once — not every page view. Large documents — stream or chunk; cache sanitized output keyed by content hash.

    Real production example

    CMS publish hook — reject if sanitizer strips >30% content length (signals attack or paste bomb):

    • Version: Pin DOMPurify version; dependabot alerts for security patches.
    html
    function publishArticle(rawHtml) {
    const clean = sanitizeArticle(rawHtml);
    if (clean.length < rawHtml.length * 0.7) {
    throw new ValidationError("Content blocked — disallowed HTML detected");
    }
    return db.save({ html: clean });
    }

    Enterprise usage

    Legal CMS — two profiles: strict (internal docs) vs moderate (customer KB with links/images). Security approves allowlist changes via PR to config repo.

    Common production failures

    Custom regex sanitizer bypassed with . Allowed iframe without src host check — embed attacker site. Old DOMPurify version mXSS in IE mode — upgraded after bug bounty report.

    Architecture review questions

    • Sanitizer allowlist defined per content type — not global permissive?
    • Server-side sanitize on save AND render?
    • DOMPurify (or equivalent) version current with CVE monitoring?
    • href/src validated against URL scheme allowlist?
    • CSP still enforced on pages rendering sanitized HTML?

    Hands-on project

    Project: Configure DOMPurify for comment allowlist; write tests with OWASP XSS payload list; verify blocked payloads don't execute in browser fixture.

    Interview questions

    Why regex HTML sanitization fails?(Advanced)

    HTML parser is error-tolerant — nested tags, attribute order mutations, SVG/MathML, encoding tricks bypass regex. mXSS: sanitizer output re-parsed differently by browser. Allowlist DOM walkers (DOMPurify) address parser-aware stripping.

    Follow-up: Allow style tag ever?

    Server vs client sanitization?(Advanced)

    Server mandatory — client can be bypassed by direct API POST. Client re-sanitize optional defense in depth for DOM sinks. Always store sanitized server-side; never trust client-cleaned HTML in DB.

    Follow-up: Markdown to HTML pipeline risks?

    Design allowlist change process for CMS team requesting iframe embeds.(Advanced)

    Security review: src domain allowlist, sandbox attribute required, separate content profile, pen-test embed breakout, CSP frame-src update, document in ADR, time-bound approval with audit of embedded URLs quarterly.

    Follow-up: Trusted Types integration?

    Try it yourself

    Edit the HTML, CSS, or JS panels — the preview updates as you type.

    Try it yourself

    Preview

    Summary

    HTML sanitization applies OWASP allowlist policies via maintained libraries like DOMPurify — staff teams define per-content-type tag/attribute profiles, sanitize server-side, and pair with CSP for defense in depth on CMS and user-generated markup.

    Ready to mark this lesson complete?Track your journey across the entire course.