Skip to content

Security Goals and Threat Model

Cure53 edited this page Jun 19, 2026 · 1 revision

Security Goals and Threat Model

What DOMFortify is trying to do

Retrofit Trusted Types protection onto code you cannot easily change. The goal is to neutralize DOM-based XSS that flows through Trusted Types HTML sinks, by sanitizing at the sink rather than at every call site, and to do it honestly: if it cannot protect you, it tells you, and it never trades a real guarantee for a comforting-but-false one.

It is a hardening layer, not a license to stop fixing XSS at the source. Treat it as defense in depth.

What it defends against

  • DOM-XSS through Trusted Types HTML sinks: innerHTML, outerHTML, insertAdjacentHTML, document.write, range.createContextualFragment, and the rest. Attacker-controlled markup reaching these is sanitized by the default policy before it lands.
  • Execution through script sinks: eval, new Function, script.src, javascript: URLs. These are refused outright unless you explicitly opt a value in.
  • mutation XSS, to the extent your sanitizer handles it. DOMFortify routes the string to the sanitizer; DOMPurify's mXSS defenses then apply.

What it assumes (the protection rests on these)

  • DOMFortify runs first and owns the default policy. The policy is winner-takes-all; if something else claims it first, DOMFortify steps aside and reports that it did not install.
  • Enforcement is on. Trusted Types has to be enforced for any of this to engage. That enforcement can come from a response header, a parse-time <meta>, or DOMFortify's own INJECT_META, but it has to be present. With enforcement off, DOMFortify is inert and says so via status().
  • The sanitizer is sound. DOMFortify is only as good as the sanitizer behind it. A current DOMPurify is the default for a reason; a permissive SANITIZER_CONFIG can re-open holes.
  • The page is not already compromised before DOMFortify loads. If attacker code runs before init(), it can claim the policy or otherwise get ahead of you.

What is out of scope

  • Sinks that are not Trusted Types sinks. Inline event handler attributes (onclick=), style sinks, and plain URL navigation (a.href = "javascript:...") are not Trusted Types sinks, so DOMFortify cannot see or stop them. Close those with a script-src that drops 'unsafe-inline'.
  • Server-side and stored XSS that does not pass through a DOM sink. DOMFortify is a browser-side sink guard, not an input filter.
  • The sanitizer's own bugs. A bypass in DOMPurify is a bypass through DOMFortify.
  • Anything when enforcement is off, when DOMFortify did not win the default policy, or on a page matched by EXCLUDE.
  • Side channels, timing, and the broader platform. DOMFortify addresses one specific class of bug well, not everything.

Trust boundary and tamper resistance

DOMFortify treats its configuration and the sanitizer as trusted, and the page content as untrusted. Within that boundary it is built to resist being quietly disabled by hostile content:

  • Native references (trustedTypes, document, location, hasOwnProperty) are captured at load, so later DOM clobbering cannot swap them out.
  • Configuration is read own-key only, so a polluted Object.prototype cannot inject a rule, point the sanitizer elsewhere, or open a script sink.
  • The internal shallow copy of your sanitizer config drops __proto__, constructor, and prototype.
  • The violation reporter (ON_VIOLATION) is wrapped so a throwing or hostile reporter cannot abort installation or turn a fail-closed sink into a thrown exception.

These reduce the chance that a partially-compromised page can neutralize DOMFortify without tripping enforcement, but they are not a substitute for loading first and keeping the sanitizer current.

Clone this wiki locally