For more than two decades, web developers have relied on innerHTML to inject dynamic HTML content into web pages. It was simple, intuitive, and wildly dangerous. Now, with the release of Firefox 148, Mozilla has thrown its weight behind a new browser-native API called setHTML() that promises to eliminate one of the most persistent and damaging classes of web vulnerabilities: cross-site scripting, or XSS. The move marks a significant moment in browser security, one that arrives with broad cross-browser consensus and years of standards work behind it.
XSS attacks remain among the most common and costly threats facing web applications. They allow attackers to inject malicious scripts into pages viewed by other users, enabling everything from session hijacking to credential theft. According to OWASP, XSS has consistently ranked among the top ten web application security risks for over a decade. The root cause is deceptively mundane: developers insert untrusted data into the DOM without adequate sanitization, often via innerHTML.
Why innerHTML Has Been a Security Liability for Decades
The problem with innerHTML is straightforward. When a developer writes element.innerHTML = userInput, the browser parses and renders whatever HTML string is provided, including any embedded <script> tags, event handlers like onerror, or other vectors for code execution. While browsers eventually stopped executing <script> tags inserted via innerHTML, countless other injection vectors remain. An attacker can craft payloads using <img src=x onerror=alert(1)> or <svg onload=...> constructs that bypass naive filtering. As Mozilla Hacks explains in its announcement, “innerHTML blindly sets content without any sanitization, making it a frequent source of XSS bugs.”
Over the years, developers have turned to third-party sanitization libraries like DOMPurify to strip dangerous elements and attributes from HTML strings before inserting them into the DOM. DOMPurify, maintained by security researcher Mario Heiderich, became the de facto standard for client-side HTML sanitization. But relying on a third-party library for a fundamental security function has always been an uncomfortable arrangement. Libraries must be kept updated, they add to page weight, and they can themselves contain bugs. The browser, which already has a full HTML parser, is better positioned to perform this work natively.
Enter the HTML Sanitizer API and setHTML()
The new setHTML() method, now shipping in Firefox 148, is part of the broader HTML Sanitizer API specification being developed through the W3C and WHATWG. According to Mozilla Hacks, the API provides a built-in, browser-native mechanism for safely inserting HTML content into the DOM. When a developer calls element.setHTML(untrustedString), the browser parses the HTML, strips out dangerous elements and attributes—such as <script>, event handler attributes, and other known XSS vectors—and inserts only the safe remainder into the DOM.
The default behavior is designed to be secure out of the box. Developers do not need to configure allowlists or blocklists unless they want to customize the sanitization. The API strips elements like <script>, <iframe>, <object>, <embed>, and attributes like onclick, onerror, and onload by default. For developers who need more granular control, the Sanitizer object can be configured with custom element and attribute allowlists. As Mozilla’s documentation notes, this means developers can permit specific elements like <b>, <i>, and <a> while blocking everything else, or they can extend the defaults to allow certain additional elements for their use case.
How setHTML() Differs From Previous Sanitization Approaches
One of the most important distinctions between setHTML() and library-based approaches is that the browser’s sanitizer operates on the parsed DOM tree, not on the raw HTML string. This is a critical difference. String-based sanitizers must anticipate every quirk of HTML parsing—nested tags, encoding tricks, parser differentials between browsers—and attackers have historically exploited mismatches between how a sanitizer parses HTML and how the browser ultimately renders it. Because setHTML() uses the browser’s own parser, there is no gap between what the sanitizer sees and what the browser renders. The parsed tree is the sanitized tree.
Mozilla’s announcement also highlights the companion method getHTML(), which serializes the current DOM content of an element back into an HTML string. Together, setHTML() and getHTML() provide a round-trip mechanism for safely handling HTML content. Additionally, the static method Document.parseHTMLUnsafe() exists for cases where developers explicitly need to parse HTML without sanitization, making the security implications of their choice visible in the method name itself—a design philosophy that favors clarity over convenience.
Cross-Browser Support and the Standards Process
Firefox 148 is not alone in supporting the Sanitizer API. Chromium-based browsers, including Google Chrome and Microsoft Edge, have been developing their own implementations. Safari’s WebKit team has also signaled interest in the specification. The API has gone through several iterations at the standards level, with earlier proposals being revised based on feedback from browser implementers and security researchers. The current specification represents a consensus approach that balances security defaults with developer flexibility.
The road to this point has not been entirely smooth. Earlier versions of the Sanitizer API proposal included a different surface area, and some implementations were shipped behind flags before being pulled back for redesign. The current iteration—centered on setHTML() as a method on Element and ShadowRoot—reflects lessons learned from those earlier attempts. According to Mozilla Hacks, the Firefox team worked closely with the specification editors and other browser vendors to ensure interoperability and a consistent security model across implementations.
What This Means for DOMPurify and Existing Sanitization Libraries
The arrival of a native sanitizer does not immediately render DOMPurify and similar libraries obsolete. Many applications will continue to support older browsers that lack the Sanitizer API, and DOMPurify offers configuration options and edge-case handling that the native API may not replicate in its initial release. However, for new applications targeting modern browsers, the calculus changes significantly. A browser-native sanitizer removes a dependency, reduces JavaScript bundle size, and eliminates an entire category of bugs related to parser differentials.
Mario Heiderich, the creator of DOMPurify, has been involved in the standards process and has publicly supported the development of a native API. The long-term expectation within the web security community is that native sanitization will become the default approach, with libraries like DOMPurify serving as polyfills or providing advanced configuration for specialized use cases. For the vast majority of developers who simply need to safely render user-generated HTML—comment systems, rich text editors, email clients—setHTML() will be sufficient and preferable.
Practical Implications for Development Teams
For engineering teams and security professionals, the practical migration path is relatively straightforward. Any code that currently uses element.innerHTML = sanitize(untrustedHTML) can be replaced with element.setHTML(untrustedHTML). The API is designed to be a drop-in replacement for the most common use case. Teams that use Content Security Policy (CSP) headers to mitigate XSS will find that setHTML() provides a complementary layer of defense. CSP prevents inline script execution at the policy level; setHTML() prevents dangerous content from entering the DOM in the first place.
Mozilla’s release notes for Firefox 148 emphasize that the feature is enabled by default with no flags required. Developers can begin using it immediately in Firefox, and feature detection is straightforward: checking for the existence of Element.prototype.setHTML allows graceful fallback to library-based sanitization in browsers that have not yet shipped the API. This incremental adoption model means teams can begin integrating the native API without breaking compatibility for users on older browsers.
The Broader Shift Toward Secure-by-Default Web APIs
The introduction of setHTML() fits within a broader trend in web platform development: making the default, easy path also the secure path. For years, the web platform offered powerful but dangerous primitives—innerHTML, document.write(), eval()—and expected developers to apply security discipline on their own. The results were predictable: XSS became endemic. The Sanitizer API represents a philosophical shift. Rather than asking developers to avoid dangerous APIs or to bolt on security after the fact, the platform now offers an API where security is the default behavior.
This approach mirrors similar trends elsewhere in the platform. The fetch() API replaced XMLHttpRequest with more sensible defaults around CORS. The Trusted Types API, another emerging standard, aims to prevent DOM-based XSS by requiring typed objects rather than raw strings in dangerous sinks. Together, these APIs represent a concerted effort by browser vendors to reduce the attack surface of the web platform itself. Firefox 148’s implementation of setHTML() is one piece of that larger effort, but it addresses one of the oldest and most damaging vulnerabilities in web development. For the millions of developers who have written innerHTML and hoped for the best, the browser is finally offering a better answer.