Native HTML Sanitization: Say Goodbye to innerHTML XSS

Computer Monitor Screen with Colorful Programming Code Lines Blurred Background Close Up

Inserting dynamic, user-generated HTML into your website has historically been one of the easiest ways to introduce cross-site scripting (XSS) vulnerabilities. For years, developers had to choose between using the highly dangerous innerHTML property, writing complex homegrown regex filters, or pulling in heavyweight external libraries like DOMPurify.

The web platform is solving this problem natively with the introduction of the HTML Sanitizer API. This API brings robust, context-aware, and highly optimized sanitization capabilities directly into modern browsers.

The Danger of the innerHTML Trap

When you use the traditional element.innerHTML = untrustedString approach, the browser parses and executes everything inside that string immediately. If an attacker passes a malicious payload, your users are instantly vulnerable:

// A classic XSS vector
const userComment = `<img src="x" onerror="alert('Hacked!')">`;
container.innerHTML = userComment; // The onerror handler executes immediately!

Libraries like DOMPurify solved this by parsing and cleaning the string in JavaScript before it ever touched the DOM. While effective, this introduces third-party dependency overhead and minor execution bottlenecks.

The Modern Way: Native Element.setHTML()

The core of the HTML Sanitizer API is the secure Element.prototype.setHTML() method. Instead of setting raw text, you pass the untrusted input directly to setHTML(), and the browser handles the parsing and sanitization in native code before inserting it into the target element.

1. Simple Safe Insertion (Built-in Defaults)

By default, calling setHTML() will completely strip high-risk elements—such as

const commentsContainer = document.getElementById("comments");
const rawUserInput = `<p>Check this out! <script>maliciousCode();</script></p>`;

// Safe: automatically strips the script tag in native code!
commentsContainer.setHTML(rawUserInput);

2. Reusable Visual Customization

If you need more control, you can construct a custom Sanitizer instance. This allows you to whitelist specific elements or attributes while letting the browser enforce standard XSS safeguards:

// Define a custom config to allow simple rich text
const customSanitizer = new Sanitizer({
  allowElements: ["p", "a", "strong", "em", "code"],
  allowAttributes: {
    href: ["a"],
    target: ["a"]
  },
  allowComments: false
});

// Any scripts or disallowed tags/attributes are stripped automatically
container.setHTML(rawUserInput, { sanitizer: customSanitizer });

Understanding the Unsafe Siblings

The browser sandbox also provides low-level APIs like Element.setHTMLUnsafe() and ShadowRoot.setHTMLUnsafe().

  • setHTML(): Safe by design. It refuses to bypass standard XSS protections under any configuration (it will always strip <script>, event handlers, etc.).
  • setHTMLUnsafe(): Designed for advanced, expert scenarios. It does not enforce built-in XSS safeguards unless you strictly filter them with a custom sanitizer or pair it with Trusted Types (TrustedHTML). Always prefer setHTML() for untrusted, user-generated inputs.

Guarding Against Evolving Specs and Environments

As of mid-2026, the HTML Sanitizer API is standardizing under the WHATWG HTML Sanitization specification and is widely supported natively in current evergreen browsers. However, to support older platforms or edge-case environments, you should always feature-detect first.

function safeInsertHTML(htmlString, targetElement) {
  if (typeof Sanitizer !== "undefined" && "setHTML" in Element.prototype) {
    targetElement.setHTML(htmlString);
  } else {
    // Fallback to DOMPurify for legacy browsers
    targetElement.innerHTML = DOMPurify.sanitize(htmlString);
  }
}
more posts:
Online video streaming platform and digital content management concept with media player interface, cloud library, and multimedia files on laptop, representing content distribution, video marketing
Code

Breaking into the Pipeline: Real-Time Frame Manipulation with the Insertable Streams for MediaStreamTrack API

The experimental Insertable Streams for MediaStreamTrack API closes a long-standing gap in web multimedia by treating live camera feeds as standard Web Streams. By piping individual VideoFrame objects through a TransformStream and recording the transformed output via MediaRecorder, developers can execute frame-by-frame processing—like real-time filtering, watermarking, and computer vision overlays—without relying on hidden canvas hacks.

Discover More »
Apps & Custom Software

What Building Our Own App Taught Us About Building Yours

When Hess WebTech built and launched Tericipe—an Android app that turns printed recipes into a searchable digital collection—in under three months, we had to live with every decision we usually advise clients on. From testing against real-world messy data and handling AI uncertainty to server-side payment security and post-launch maintenance, here are five crucial lessons from our own build that apply to almost any software project.

Discover More »