Firefox 148's setHTML: The XSS Protection We Should Have Had Years Ago
Firefox just shipped setHTML in version 148, replacing the notorious innerHTML with something that actually sanitizes by default. Here's why this matters and what it means for your security posture.
Firefox 148 just dropped something that should have existed a decade ago: the setHTML() method. If you've ever written element.innerHTML = userInput and immediately felt that little voice in your head screaming "sanitize that!", this is for you.
The problem we've been living with
For years, innerHTML has been the default way to inject HTML into the DOM. It's fast, it's simple, and it's catastrophically insecure if you're not careful. The issue? It executes scripts by default. Feed it user input without proper sanitization, and you've just handed an attacker the keys to your application.
The classic blunder:
// DON'T DO THIS
element.innerHTML = `<div>${userComment}</div>`;
If userComment contains <img src=x onerror=alert('XSS')>, congratulations you just got owned.
What setHTML actually does
Firefox 148 introduces setHTML() as part of the Sanitizer API. Unlike innerHTML, it sanitizes content by default using a built-in sanitizer before injecting it into the DOM.
// This is safe
element.setHTML(userComment);
The browser strips out anything dangerous - script tags, event handlers, javascript: URLs - before it ever touches the page. No library needed. No regex hacks. Just native, browser-level protection.
Why this took so long
The Sanitizer API has been in development for years. The core challenge? Balancing security with backward compatibility. Developers have been relying on innerHTML's "execute everything" behavior for legitimate use cases. Changing that default would break half the internet.
The solution: introduce a new method. innerHTML stays exactly as dangerous as it's always been. setHTML() gives you the safe alternative. You opt in to security.
What this means for your codebase
If you're working on a production web app, here's the practical takeaway:
-
Start using setHTML() for user-generated content. Anywhere you're taking input from users and rendering it back, switch to the new API.
-
innerHTML isn't going anywhere. You still need it for trusted content you control. Just be intentional about when you use it.
-
Check browser support. Firefox 148 has it now. Chrome and Safari will follow (the spec is standardized). Use feature detection:
javascriptif (element.setHTML) {
element.setHTML(content);
} else {
// Fallback to a sanitization library
element.innerHTML = DOMPurify.sanitize(content);
} -
This doesn't replace CSP. Content Security Policy is still your first line of defense. setHTML is defense in depth.
The bigger picture
This is part of a broader shift in web security: making the secure option the easy option. For too long, doing the right thing required extra dependencies (DOMPurify, sanitize-html) and extra cognitive load. Developers are human. When the insecure path is easier, people will take it.
By baking sanitization into the platform, Firefox (and eventually the rest) is reducing the friction to do the right thing. That's how you actually move the needle on security at scale.
What I'm watching
The next question is adoption. How long until this becomes a standard recommendation in security audits? How long until frameworks like React or Vue start using it internally?
My bet: within 18 months, using innerHTML for user content will be a critical finding in any halfway-decent security audit. And good riddance.
If you're a Principal Engineer or tech lead, start auditing your codebase now. Find every innerHTML call. Ask yourself: is this trusted content? If not, it's a candidate for migration.
The tools to write secure code are finally getting better. Time to use them.
Get new posts in your inbox
Architecture, performance, security. No spam.
Keep reading
Building Production-Ready MCP Servers
MCP servers are everywhere. Production-ready ones aren't. Here's the architecture I use after running MCP in real workloads: error boundaries, state isolation, security hardening, and scaling patterns that actually hold up.
Your Google API Keys Just Became Gemini Credentials (And Nobody Told You)
Google told developers API keys aren't secrets. Then Gemini changed the rules. Truffle Security found 2,863 live keys on public websites that now access private Gemini endpoints, including keys belonging to Google itself. The attack is a single curl command.
AI Can't Audit Your Binaries Yet
The best AI model finds 49% of backdoors in compiled binaries. With a 22% false positive rate. Here's what that means for your supply chain security strategy.