7 ms·
Goodbye InnerHTML, Hello SetHTML: Stronger XSS Protection in Firefox 148
- antonyh 8mo agoA rather deceptive title, given that 'innerHTML' isn't going away.
- jandrese 8mo agoI think the title is trying to convince you to switch from InnerHTML to SetHTML.
- entuno 8mo agoThis kind of thing always makes me nervous, because you end with a mix of methods where you can (supposedly) pass arbitrary user input to them and they'll safely handle it, and methods where you can't do that without introducing vulnerabilities - but it's not at all clear which is which from the names. Ideally you design that in from the state, so any dangerous functions are very clearly dangerous from the name. But you can't easily do that down the line. I'm also rather sceptical of things that "sanitise" HTML, both because there's a long history of them having holes, and because it's not immediately clear what that means, and what exactly is considered "safe".
- voxic11 8mo agoThe idea is you wouldn't mix innerHTML and setHTML, you would eliminate all usage of innerHTML and use the new setHTMLUnsafe if you needed the old functionality.
- post-it 8mo ago> you would eliminate all usage of innerHTML The mythical refactor where all deprecated code is replaced with modern code. I'm not sure it has ever happened. I don't have an alternative of course, adding new methods while keeping the old ones is the only way to edit an append-only standard like the web.
- noduerme 8mo agoFinally, a good use case for AI.
- josefx 8mo agoWouldn't AI be trained on data using innerHTML?
- Aachen 8mo agoMy experience is that they somehow print quite modern code despite things like ES6 being too new to be standard knowledge even for me and I'm not even middle-aged yet Maybe the last 10 years saw so much more modern code than the last cumulative 40+ years of coding and so modern code is statistically more likely to be output? Or maybe they assign higher weights to more recent commits/sources during training? Not sure but it seems to be good at picking this up. And you can always feed the info into its context window until then
- skeeter2020 8mo agoThis is not my experience. Claude has been happily generating code over the past week that is full of implicit any and using code that's been deprecated for at least 2 years. >> Maybe the last 10 years saw so much more modern code than the last cumulative 40+ years of coding and so modern code is statistically more likely to be output? The rate of change has made defining "modern" even more difficult and the timeframe brief, plus all that new code is based on old code, so it's more like a leaning tower than some sort of solid foundation.
- SahAssar 8mo agoES6 is 11 years old. It's not that new.
- Aachen 8mo agoHence the example of how long it takes non-LLMs to pick that up, whereas LLMs seem to get it despite there being loads of old code out there See also my reply to the sibling comment with the same remark https://news.ycombinator.com/item?id=47151211 https://news.ycombinator.com/item?id=47151211 My mistake for saying 10 instead of 11 years btw, but I don't think it changes the point
- croes 8mo agoIf I need the old functionality why not stick to innerHTML?
- orf 8mo agobecause the "unsafe" suffix conveys information to the reader, whereas `innherHTML` does not?
- goatlover 8mo agoAny potential reader should be familiar with innerHTML.
- kennywinker 8mo agoRight. Like how any potential reader is familiar with the risks of sql injection which is why nothing has ever been hacked that way. Or how any potential driver is familiar with seat belts which is why everybody wears them and nobody’s been thrown from a car since they were invented.
- deleted 8mo ago[deleted]
- orf 8mo agoyes, and bugs shouldn't exist because everyone should be familiar with everything.
- croes 8mo agoBut if some are marked unsafe and others are not it gives a false sense of security if something is not marked unsafe.
- orf 8mo agoSo we shouldn’t mark anything as unsafe then? And give no indication whatsoever? The issue isn’t that the word “safe” doesn’t appear in safe variants, it’s that “unsafe” makes your intentions clear: “I know this is unsafe, but it’s fine because of X and Y”.
- reddalo 8mo agoYou can't rename an existing method. It would break compatibility with existing websites.
- extraduder_ire 8mo agoI looked up setHTMLUnsafe on MDN, and it looks like its been in every notable browser since last year. Good idea to ship that one first, when it's easier to implement and is going to be the unsafe fallback going forward.
- onion2k 8mo agoI looked up setHTMLUnsafe on MDN, and it looks like its been in every notable browser since last year. Oddly though, the Sanitizer API that it's built on doesn't appear to be in Safari. https://developer.mozilla.org/en-US/docs/Web/API/Sanitizer https://developer.mozilla.org/en-US/docs/Web/API/Sanitizer
- DoctorOW 8mo agoThey do link the default configuration for "safe": https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration https://wicg.github.io/sanitizer-api/#built-in-safe-default-... But I agree, my default approach has usually been to only use innerText if it has untrusted content: So if their demo is this: container.SetHTML(`<h1>Hello, {name}</h1>`); Mine would be: let greetingHeader = container.CreateElement("h1"); greetingHeader.innerText = `Hello, {name}`;
- itishappy 8mo agoWhat if I wanted an <h2>? Edit: I don't mean this flippantly. If I want to render, say, my blog entry on your site, will I need to select every markup element from a dropdown list of custom elements that only accept text a la Wordpress?
- post-it 8mo agorealSetSafeHTML()
- jncraton 8mo agoYou are right that the concept of "safe" is nebulous, but the goal here is specifically to be XSS-safe [1]. Elements or properties that could allow scripts to execute are removed. This functionality lives in the user agent and prevents adding unsafe elements to the DOM itself, so it should be easier to get correct than a string-to-string sanitizer. The logic of "is the element currently being added to the DOM a <script>" is fundamentally easier to get right than "does this HTML string include a script tag". [1] https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML https://developer.mozilla.org/en-US/docs/Web/API/Element/set...
- entuno 8mo agoIt's certainly an improvement over people trying to homebrew their own sanitisers. But that distinction of being XSS-safe is a potentially subtle one, and could end up being dangerous if people don't carefully consider whether XSS-safe is good enough when they're handling arbitrary users input like that.
- intrasight 8mo agoAlso has made me nervous for years that there's been no schema against which one can validate HTML. "You want to validate? Paste your URL into the online validation tool."
- Dylan16807 8mo agoThis help? https://github.com/validator/validator https://github.com/validator/validator But for html snippets you can pretty much just check that tags follow a couple simple rules between <> and that they're closed or not closed correctly.
- intrasight 8mo agoThat app does look helpful!
- snowhale 8mo ago[dead]
- pornel 8mo agoBTW, HTML allows inline SVG with an XML-flavored syntax that interprets <script/> and <title> differently. It's a goldmine for sanitizer escapes. There are completely bonkers syntax switching and error recovery rules that interact with parsing modes (there's even an edge case where a particular attribute value switches between HTML and XML-ish parsing rules). Don't even try to allow inline <svg> from untrusted sources! (and then you still must sanitise any svg files you host)
- kccqzy 8mo agoIf you just serve SVGs through <img> tag it’ll be much safer. I never understood the appeal of inline <svg> anyways.
- rwj 8mo agoInline reduces round trips.
- toast0 8mo agoYou can use img with a data url?
- lenkite 8mo agoInline SVG is stylable with CSS styles in the same HTML page.
- runarberg 8mo agoAlso animatible with the same context (Animation API, etc.) as the parent page, so different SVGs can influence each other’s animations.
- cxr 8mo ago
- noduerme 8mo agoSome sanitization is better than none? If you're relying on the browser to handle it for you, you're already in a lot of trouble.
- deleted 8mo ago[deleted]
- jaffathecake 8mo agofwiw, if you serve your page with: Content-Security-Policy: require-trusted-types-for 'script' …then it blocks you from passing regular strings to the methods that don't sanitize.
- Cthulhu_ 8mo agoIdeally you should be able to set a global property somewhere (as a web developer) that disallows outdated APIs like `innerHTML`, but with the Big Caveat that your website will not work on browsers older than X. But maybe there's web standards for that already, backup content if a browser is considered outdated.
- afavour 8mo agoI like the idea of that. But I imagine linting rules are a much more immediate answer in a lot of projects.
- staticassertion 8mo agoDoesn't using TrustedTypes basically do that? I'm not really web-y, someone please correct me if I'm off.
- madeofpalk 8mo agoYup, this is basically what TrustedTypes is for!
- cxr 8mo agoIt's not an "outdated API". It's still good for what it was always meant for: parsing trusted, application-generated markup and atomically inserting it into the content tree as a replacement for a given element's existing children. > set a global property somewhere (as a web developer) that disallows[…] `innerHTML` Object.defineProperty(Element.prototype, "innerHTML", { set: (() => { throw Error("No!") }) }); (Not that you should actually do this—anyone who has to resort to it in their codebase has deeper problems.)
- onion2k 8mo agoit's not at all clear which is which from the names There's setHTML and setHTMLUnsafe. That seems about as clear as you can get.
- hahn-kev 8mo agoBut you can use InnerHTML to set HTML and that's not safe.
- onion2k 8mo agoAt this point that API has been around for decades and is probably impossible to deprecate without breaking fairly large amounts of the web. The only option is to introduce a new and better API, and maybe eventually have the browser throw out console warnings if a page still uses the old innerHTML API. I doubt any browser vendor will be gung ho enough to actually remove it for a very long time.
- entuno 8mo agoIf that'd been the design from the start, then sure. But it's not at all obvious that setHTML is safe with arbitrary user input (for a given value of "safe") and innerHTML is dangerous.
- HWR_14 8mo agoThat's why I only allow user input of alphanumeric ascii characters. No need to worry about sanitation then, and you can just remove all the characters that don't match. (It's a joke, but it is also 100% XSS, SQL injection, etc. safe and future proof)
- thaumasiotes 8mo ago> I'm also rather sceptical of things that "sanitise" HTML, both because there's a long history of them having holes, and because it's not immediately clear what that means, and what exactly is considered "safe". What is safe depends on where the sanitized HTML is going, on what you're doing with it. It isn't possible to "sanitize HTML" after collecting it so that, when you use it in the future, it will be safe. "Safe" is defined by the use. But it is possible to sanitize it before using it, when you know what the use will be.
- cxr 8mo ago> it's not at all clear which is which from the names. Ideally you design that in from the [start] It was, and there is: setting elementNode.textContent is safe for untrusted inputs, and setting elementNode.innerHTML is unsafe for untrusted inputs. The former will escape everything, and the latter won't escape anything. You are right that these "sanitizers" are fundamentally confused: > "HTML sanitization" is never going to be solved because it's not solvable.¶ There's no getting around knowing whether or any arbitrary string is legitimate markup from a trusted source or some untrusted input that needs to be treated like text. This is a hard requirement. <https://news.ycombinator.com/item?id=46222923 https://news.ycombinator.com/item?id=46222923> The Web platform folks who are responsible for getting fundamental APIs standardized and implemented natively are in a position to know better, and they should know better. This API should not have made it past proposal stage and should not have been added to browsers.
- Dylan16807 8mo ago> There's no getting around knowing whether or any arbitrary string is legitimate markup from a trusted source or some untrusted input that needs to be treated like text. This is a hard requirement. It is not a hard requirement that untrusted input is "treated like text". And this API lets you customize exactly what tags/attributes are allowed in the untrusted input. That's way better than telling everyone to write their own; it's not trivial.
- cxr 8mo ago[flagged]
- Dylan16807 8mo agoI don't see how I differed from what you said? You divided strings going into HTML into two categories, where one category uses textContent and the other category uses innerHTML. My point is to disagree with those categories, not whatever subtle thing you're taking issue with.
- 8mo ago
- duxup 8mo agoYeah someone tells me something has been made “safe” is nice but unless I know exactly what that means … it’s easy to say safe by someone who doesn’t have to deal with it when the bad corner case happens. Oh and it’s safe… in this browser… not that one, so this idea of safety is kinda dead to me for now.
- simonw 8mo agoGreat to see this start to show up, but it looks like it will be a while before browser support is widely distributed enough to rely on it being present: https://caniuse.com/mdn-api_element_sethtml https://caniuse.com/mdn-api_element_sethtml
- jraph 8mo agoIndeed, as any browser API, it might be for in a few years (months if happy with the most recent versions), and we may have polyfills in the meantime.
- deleted 8mo ago[deleted]
- tuyiown 8mo agoI wouldn't advise polyfills on this one, it entirely depends on the browser ability to evaluate cross scripting and cross origin rule on a arbitrary snippet. This is not a convenience API.
- tuyiown 8mo agoThis is nice. The best part is that all aspects of network access are now properly controlled so that security transitioned from a chain of trusted code to a chain of trusted security setup on hosts, with existing workable safe defaults.
- bingemaker 8mo agoNice one. Will there be any impact on __dangerouslySetInnerHTML (React)?
- octoclaw 8mo ago[dead]
- Aachen 8mo agoSo you can still inject <h1> or <br><br><br>... etc into your username, in the given example Preventing one bug class (script execution) is good, but this still allows arbitrary markup to the page (even <style> CSS rules) if I'm reading the docs correctly. You could give Paypal a fresh look for anyone who opens your profile page, if they use this. Who would ever want this?
- embedding-shape 8mo ago> So you can still inject <h1> or <br><br><br>... etc into your username, in the given example How exactly, given that setHTML sanitizes the input? If you don't want to have any HTML tags allowed, seems you can configure that already? https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration https://wicg.github.io/sanitizer-api/#built-in-safe-default-...
- Aachen 8mo ago> How exactly, given that setHTML sanitizes the input? The article says that the output is: <h1>Hello my name is</h1> So it keeps (non-script) html tags (and presumably also attributes) in the input. Idk how you're asking "how" since it's the default behavior Stripping HTML tags completely has always been possible with the drop-in replacement `textContent`. Making a custom configuration object for that is much more roundabout
- embedding-shape 8mo agoYes, because that's the default configuration, if you don't want that, stop using the default configuration? It's still sanitizing away the common XSS holes, hence it's a safer alternative to .innerHTML, and a more flexible alternative to .innerText
- Aachen 8mo agoShouldn't use innerText anyway (nonstandard, worse performance, tries to parse the HTML and gives you unexpected behavior if e.g. a style is set that makes an element invisible but still has text inside, doesn't work on all DOM nodes...) I can see how it's a way of allowing some tags like bold and italic without needing a library or some custom parser, but I didn't understand what the point of this default could be and so why it exists (a sibling comment proposed a plausible answer: hardening on top of another solution) > Yes, because that's the default configuration, if you don't want that, stop using the default configuration? "don't use it if it's not what you want" is perhaps the silliest possible answer to the question "what's the use-case for this"
- cogman10 8mo agoSeems like this has a bunch of footguns. Particularly if you interact with the Sanitizer api, and particularly if you use the "remove" sanitizer api. Don't get me wrong, better than nothing, but also really really consider just using "setText" instead and never allow the user to add any sort of HTML too the document.
- evilpie 8mo agoUsing an allowlist based Sanitizer you are definitely less likely to shoot yourself in the foot, but as long as you use setHTML you can't introduce XSS at least.
- GalaxyNova 8mo agoIt's worse than nothing, since inevitably people will use this thinking it's 100% safe when it's not.
- lelanthran 8mo ago> never allow the user to add any sort of HTML too the document. What about when the author of the page wants to add large html fragments to the page? Are you saying that you cannot think of a single use for this, considering how often innerHTML is being used?
- dbvn 8mo agoat what point can we consider the development of "set this element's text/html" to be done?
- Aachen 8mo agoWhen browsers implement a variant that lets you separate data and code perhaps. That's what I expected when reading the headline: setHtml(code, data, data, ...), just like parameterised SQL works: prepare("select rowid from %s where time < %n", tablename, mynumber) This new method they've cooked up would be called eval(code,options) if html was anything other than a markup language
- itishappy 8mo agotablenames cannot be parameterized in SQL https://stackoverflow.com/questions/78516750/parametrize-table-name-in-sql-query https://stackoverflow.com/questions/78516750/parametrize-tab...
- shadowgovt 8mo agoOh, that's nice-to-have. Good work, Mozilla. It would close the loop better if you could also use policy to switch off innerHTML in a given page, but definitely a step in the right direction for plain-JavaScript applications.
- bryanrasmussen 8mo agois there any situation where innerHTML would be preferable? I could suppose it might be more performant and so if you were constructing something that was not open to XSS it might theoretically be better (with the usual caveat that people always make mistakes about this kind of thing)
- _slih 8mo agonaming the old behavior setHTMLUnsafe is what did it for me. security features that require developers to opt in don't work. making the unsafe path feel unsafe does.
- shevy-java 8mo agoWell, the name SetHTML, or let's say: .set_html() Makes objectively more sense than: .inner_html() .inner_html = .set_inner_html() It is a fairly small thing, but ... really. One day someone should clean up the mess that is JavaScript. Guess it will never happen, but JavaScript has so many strange things ... I understand that this here is about protection against attacks rather than a better API design, but really - APIs should ideally be as great as possible the moment they are introduced and shown to the public.
- lloydatkinson 8mo agoTo be pedantic that’s the DOM API, which is exposed to JavaScript. The DOM API has always felt like, and still does, it was written by people that have never made an API.
- pier25 8mo agoI don't think that's pedantic. Seems like a valid objection to me. So many issues in the client JS world originate from insufficient or bad browser APIs.
- lloydatkinson 8mo agoI don’t really think it’s pedantic it’s just that unless you preface a lot of comments on HN these days, you’ll get a lot of whataboutism and straw man arguments.
- giancarlostoro 8mo agoMy corporate firewall blocks it due to the "hacks" in the subdomain / url. This is silly.
- ok123456 8mo agoThat's why the DNS for hackernews is news.ycombinator.com and not hackernews.org
- giancarlostoro 8mo agonever even considered that to be honest.
- dogtimeimmortal 8mo agoTitle was a bit rage-baity. And I think you can already do sanitation by writing a function to check input before passing it to innerHTML? This really just seems like another attempt at reinventing the wheel. Somewhat related, I find it ironic how i cannot browse hacks.mozilla.org in my old version of firefox("Browser not supported"). Also, developer.mozilla.org loads mangled to various degrees in current versions of palemoon, basilisk, and seamonkey It's like there is some sort of "browser cartel" trying to screw up The Web.
- Retr0id 8mo ago> you can already do sanitation by writing a function to check input before passing it to innerHTML This is like saying C is memory safe as long as your code doesn't have any bugs. More saliently, it does not consider parser differentials.
- dvh 8mo agoKids in the '90s: SQL("select * from user where name = " + name); Kids in the '20s: div.innerHTML = "Hello " + user.name;
- Legend2440 8mo agoKids in the '30s: "Summarize this email: " + email.contents Prompt injection is just the same problem on a new technology. We didn't learn anything from the 90s.
- pier25 8mo agoTangential but it's amazing in 2026 browsers still don't ship a native DOM morph/merge API like morphdom or idiomorph.
- pyrolistical 8mo agoAnd for those who want a better innerHTML, use insertAdjacentHTML https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML https://developer.mozilla.org/en-US/docs/Web/API/Element/ins... I don’t ever use it with user input, but use it often when building SPA without frameworks
- austin-cheney 8mo agoAnother solution is just use this at the start of your code: delete Element.prototype.innerHTML; Then assignments to innerHTML do not modify the element's textContent or child node list and assignments to it will not throw an error.
- umairnadeem123 8mo ago[dead]
- jjcm 8mo agoWhat I really want is a <sandbox> element that can safely run dangerous code, not something that modifies dangerous code. Iframes have significant restrictions as they can’t flow with the DOM. With AI and the increase in dynamic content, there’s going to be even more situations where you run untrusted code. I want configurable encapsulation.
- Avamander 8mo agoWhat's the benefit over Trusted Types?