[HN Gopher] Why the Sanitizer API is just `setHTML()`
___________________________________________________________________
Why the Sanitizer API is just `setHTML()`
Author : birdculture
Score : 107 points
Date : 2025-12-08 22:37 UTC (2 days ago)
(HTM) web link (frederikbraun.de)
(TXT) w3m dump (frederikbraun.de)
| brainbag wrote:
| With context, this article is more interesting than the title
| might imply.
|
| > The Sanitizer API is a proposed new browser API to bring a safe
| and easy-to-use capability to sanitize HTML into the web platform
| [and] is currently being incubated in the Sanitizer API WICG,
| with the goal of bringing this to the WHATWG.
|
| Which would replace the need for sanitizing user-entered content
| with libraries like DOMPurify by having it built into the
| browser's API.
|
| The proposed specification has additional information:
| https://github.com/WICG/sanitizer-api/
| mubou2 wrote:
| The author really needs to start with that. They say "the API
| that we are building" and assume I know who they are and what
| they're working on, all the way until the very bottom. I just
| assumed it's some open source library.
|
| > HTML parsing is not stable and a line of HTML being parsed
| and serialized and parsed again may turn into something rather
| different
|
| Are there any examples where the first approach (sanitize to
| string and set inner html) is actually dangerous? Because it's
| pretty much the only thing you can do when sanitizing server-
| side, which we do _a lot_.
|
| Edit: I also wonder how one would add for example rel="nofollow
| noreferrer" to links using this. Some sanitizers have a "post
| process node" visitor function for this purpose (it already has
| to traverse the dom tree anyway).
| tobr wrote:
| > They say "the API that we are building" and assume I know
| who they are and what they're working on, all the way until
| the very bottom.
|
| This is a common and rather tiresome critique of all kinds of
| blog posts. I think it is fair to assume the reader has a bit
| of contextual awareness when you publish on your personal
| blog. Yes, you were linked to it from a place without that
| context, but it's readily available on the page, not a
| secret.
| mubou2 wrote:
| Well that's... certainly a take. But I have to disagree.
| Most traffic coming to blog posts is not from people who
| know you and are personally following your posts, they're
| from people who clicked a link to the article someone
| shared or found it while googling something.
|
| It's not hard to add one line of context so readers aren't
| lost. Here, take this for example, combining a couple parts
| of the GitHub readme:
|
| > For those who are unfamiliar, the Sanitizer API is a
| proposed new browser API being incubated in the Sanitizer
| API WICG, with the goal of bringing this to the WHATWG.
|
| Easy. Can fit that in right after "this blog post will
| explain why", and now everyone is on the same page.
| swiftcoder wrote:
| > Most traffic coming to blog posts is not from people
| who know you and are personally following your posts
|
| Do we have data to back that up? Anecdotally the blogs I
| have operated over the years tend to mostly sustain on
| repeat traffic from followers (with occasional bursts of
| external traffic if something trends on social media)
| tobr wrote:
| > It's not hard
|
| It's also not hard to look around for a few seconds to
| find that information, is my point.
| LegionMammal978 wrote:
| They had a link in their post [0]: it seems like most of the
| examples are with HTML elements with wacky contextual parsing
| semantics such as <svg> or <noscript>. Their recommendation
| for server-side sanitization is "don't, lol", and they don't
| offer much advice regarding it.
|
| Personally, my recommendation in most cases would be
| "maintain a strict list of common elements/attributes to
| allow in the serialized form, and don't put anything weird in
| that list: if a serialize-parse roundtrip has the remote
| possibility of breaking something, then you're allowing too
| much". Also, "if you want to mutate something, then do it in
| the object tree, not in the serialized version".
|
| [0] https://www.sonarsource.com/blog/mxss-the-vulnerability-
| hidi...
| mubou2 wrote:
| Ah, I see what they're talking about. That's a good
| article; my brain totally skipped over that link. Thanks.
| tlb wrote:
| setHTML needs to support just about every element if it's
| going to be the standard way of rendering dynamic content.
| Certainly <svg> has to work or the API isn't useful.
|
| SanitizeHTML functions in JS have had big security holes
| before, around edge cases like null bytes in values, or
| what counts as a space in Unicode. Browsers decided to be
| lenient in what they accept, so that means any serialize-
| parse chain creates some risk.
| LegionMammal978 wrote:
| If you're rendering dynamic HTML, then either the source
| is authorized to insert arbitrary dynamic content onto
| the domain, or it isn't. And if it isn't, then you'll
| always have a hard time unless you're as strict as
| possible with your sanitization, given how many nonlocal
| effects can be embedded into an HTML snippet.
|
| The more you allow, the less you know about what might
| happen. E.g., <svg> styling can very easily create
| clickjacking attacks. (If I wanted to allow SVGs at all,
| I'd consider shunting them into <img> tags with data
| URLs.) So anyone who does want to use these more
| 'advanced' features in the first place had better know
| what they're doing.
| bffjjfjf wrote:
| That overly reductive thinking can go back to the 80s
| before we had learned any lessons. There are degrees of
| trust. Binary thinking invites dramatic all or nothing
| failures.
| crote wrote:
| > Are there any examples where the first approach (sanitize
| to string and set inner html) is actually dangerous?
|
| The article links to [0], which has some examples of
| instances in which HTML parsing is context-sensitive. The
| _exact same string_ being put into a <div> might be totally
| fine, while putting it inside a <style> results in XSS.
|
| [0]: https://www.sonarsource.com/blog/mxss-the-vulnerability-
| hidi...
| masklinn wrote:
| > Are there any examples where the first approach (sanitize
| to string and set inner html) is actually dangerous?
|
| The term to look for is "mutation xss" (or mxss).
| crote wrote:
| Yeah, I was expecting something closer to "because that's what
| people Google for".
|
| A big part of designing a security-related API is making it
| really easy and obvious to do the secure thing, and hide the
| insecure stuff behind a giant "here be dragons" sign. You want
| people to _accidentally_ do the right thing, so you call your
| secure and insecure functions "setHTML" and "setUnsafeHTML"
| instead of "setSanitizedHTML" and "setHTML".
| cess11 wrote:
| get_magic_quotes_gpc() and mysql_real_escape_string() had
| quite a bit to teach in this area.
| some_furry wrote:
| Both of those functions were deprecated years ago.
|
| mysql_real_escape_string() was removed in PHP 7.0.
|
| get_magic_quotes_gpc() was removed in PHP 8.0.
|
| https://www.php.net/mysql_real_escape_string
|
| https://www.php.net/get_magic_quotes_gpc
|
| The current minimum PHP version that is supported for
| security fixes by the PHP community is 8.1:
| https://www.php.net/supported-versions.php
|
| If you're still seeing this in 2025 (going on 2026), there
| are other systemic problems at play besides the PHP code.
| guessmyname wrote:
| 100%... it's like Rust's "unsafe" package, or Rust reqwest
| package naming things like danger_accept_invalid_certs(true)
| and danger_accept_invalid_hostnames(true) - https://docs.rs/r
| eqwest/latest/reqwest/struct.ClientBuilder....
| cobbal wrote:
| Makes sense. I think this is a variant of the "parse, don't
| validate" motto, but is more "parse, don't parse-serialize-parse"
| in the implementation.
| nayuki wrote:
| > HTML parsing is not stable and a line of HTML being parsed and
| serialized and parsed again may turn into something rather
| different
|
| This is why people should really use XHTML, the strict XML
| dialect of HTML, in order to avoid these nasty parsing surprises.
| It has the predictable behavior that you want.
|
| In XHTML, the code does exactly what it says it does. If you
| write <table><a></a></table> like the example on the mXSS page,
| then you get a table element and an anchor child. As another
| example, if you write <table><td>xyz</td></table>, that's exactly
| what you get, and there are no implicit <tbody> or <tr> inserted
| inside.
|
| It's just wild as I continue to watch the world double down for
| decades on HTML and all its wild behavior in parsing.
| Furthermore, HTML's syntax is a unique snowflake, whereas XML is
| a standardized language that just so happens to be used in SVG,
| MathML, Atom, and other standards - no need to relearn syntax
| every single time.
| bayesnet wrote:
| I don't think this is right. XHTML guarantees well-formedness
| (matched closing tags et al) but doesn't do anything for
| validity. It's not semantically valid for <td> to be a direct
| child of <table>, so the user agent has to make the call as to
| what to display regardless of the (X)HTML flavor. The
| alternative is parsing failure on improperly nested HTML which
| I don't think is desirable.
| intrasight wrote:
| > The alternative is parsing failure on improperly nested
| HTML which I don't think is desirable.
|
| It was that decision that resulted in the current mess.
| Browser vendors could have given us a grace period to fix
| HTML that didn't validate against the schema. Instead they
| said "there is no schema"
| bayesnet wrote:
| The issue as I see it is that XML schemas are fine[0] for
| immutable documents but not suited for dynamic content. As
| a user it would be extraordinarily frustrating for a site
| or web app to break midflow because of a schema validation
| failure after a setHTML call or something.
|
| [0]: I've worked with XML schemas a lot and have grown to
| really dislike them actually but that's neither here nor
| there
| favorited wrote:
| You might as well complain about Betamax. XHTML is not the
| future.
| recursive wrote:
| HTML is also a standardized language.
| philipwhiuk wrote:
| The downside of a new method is that it leaves innerHtml as a
| source of future security issues.
| cluckindan wrote:
| Yes, one could simply make a setter for innerHTML which calls
| setHTML(). No code changes needed.
| masklinn wrote:
| That breaks existing usages of innerhtml which may
| legitimately need its more dangerous features.
| cxr wrote:
| It seems obvious enough that parent is talking about
| changing the behavior of innerHTML within their own
| application, not for browser makers to change the
| implementation. It's unfair to take the most uncharitable
| interpretation and upbraid the other commenter for being
| insufficiently defensive[1] when they pressed "reply".
|
| 1. <https://pchiusano.github.io/2014-10-11/defensive-
| writing.htm...>
| masklinn wrote:
| > It seems obvious
|
| Doesn't seem obvious unless your dutch.
|
| Especially as the first things _I_ would think obvious
| is: if breaking the behaviour of innerHTML is not a
| concern for your software why keep it at all? Delete the
| property or make it readonly.
| cxr wrote:
| > Doesn't seem obvious unless your dutch.
|
| I don't know what that means.
|
| > if breaking the behaviour of innerHTML is not a concern
| for your software why keep it at all?
|
| For the reason that they said.
| crote wrote:
| Yes, but you can also easily lint on it: _all_ uses of
| `context.innerHTML` are now suspect and should get a suggestion
| to use `context.setHTML` instead.
|
| With `const clean = DOMPurify.sanitize(input);
| context.innerHTML = clean;` your linter suddenly needs to do
| complex code analysis and keep track if each variable passed to
| `context.innerHTML` is clean or tainted.
| wbobeirne wrote:
| I feel like calling this a downside implies there's an
| alternative, but there's no way that `innerHtml`'s behavior
| could be changed. There are a lot of valid reasons for
| arbitrary HTML to be set, and changing that would break so many
| things.
| cortesoft wrote:
| There could be a better name for it? like
| `innerSanitizedHTML` or something, that makes it clear what
| the difference between the two calls are. There is nothing in
| the wording of setHTML that makes it clear it sanitizes where
| innerHTML doesn't.
| uallo wrote:
| Combine it with enforced Trusted Types:
|
| https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Typ...
| jamesbvaughan wrote:
| Aside from the article's content, I really like the inline
| exercise for the reader with the hidden/expandable answer
| section. It's fun and it successfully got me to read the
| proceeding section more closely than I would have otherwise.
| bikeshaving wrote:
| This is interesting. The argument which I'm gleaning from the
| essay is that the old proposed API of having an intermediary new
| Sanitizer() class with a sanitize(input) method which returns a
| string is actually insecure because of mutated XSS (MXSS) bugs.
|
| The theory is that the parse->serialize->parse round-trip is not
| idempotent and that sanitization is element context-dependent, so
| having a pure string->string function opens a new class of
| vulnerabilities. Having a stateful setHTML() function defined on
| elements means the HTML context-specific rules for tables, SVG,
| MathML etc. are baked in, and eliminates double-parsing errors.
|
| Are MXSS errors actually that common?
| cxr wrote:
| > This is pretty similar to the Sanitizer that I wanted to build
| into the browser: [...] But that is NOT the Sanitizer we ended up
| with.P And the reason is essentially Mutated XSS (mXSS). To
| quickly recap, the idea behind mXSS is[...]
|
| No, the reason is that the problem is underspecified and
| unsatisfiable.
|
| The whole notion of HTML "sanitization" is the ultimate "just do
| what I mean". It's the customer who cannot articulate what they
| need. It's <<Hey, how about if there were some sort of `import
| "nobugs"`?>>
|
| "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. (And if you already have this information, then the
| necessary tools have been available for years--decades, even:
| `innerHTML` and `textContent`--or if you don't like the latter,
| then it's trivial to write your own `escapeText` subroutine
| that's correct, well-formed, and sound.) No new DOMPurify
| alternative or native API baked into the browser is going to
| change this, ever.
| bffjjfjf wrote:
| It sounds like you didn't understand the post... or more
| likely, didn't even read it.
| IshKebab wrote:
| > Traverse the HTML fragment and remove elements as configured.
|
| Well this is clearly wrong isn't it? You need a whitelist of
| elements, not a blacklist. That lesson is at least 2 decades old.
| jkrems wrote:
| I mean... "as configured" can me either an allow OR a denylist.
| That sentence doesn't really prescribe doing it one way or the
| other..? You have to parse the denylisted elements because they
| will affect the rest of the parse, so you _have_ to remove them
| afterwards in the general case.
| IshKebab wrote:
| Looks like it supports both actually:
| https://wicg.github.io/sanitizer-api/#sanitization
|
| That's better than _only_ supporting `removeElements`, but it
| really shouldn 't support it at all.
| socketcluster wrote:
| This is a good API. I hope it gains adoption in at least one
| browser, that way other browsers which don't adopt it will be
| called 'insecure'... Which would be warranted IMO... People have
| been wanting the ability to inject safe HTML for almost as long
| as JavaScript existed.
|
| Seriously, we got CSP before setHTML() WTF!
|
| CSP is nasty. Removing essential functionality to mitigate
| possible security flaws, ignoring the developer's intent. CSP is
| like taping your mouth shut to lose weight... But you still sit
| through 3 meals a day... Basically smashing the food against your
| face.
| dec0dedab0de wrote:
| So is the usecase for this that you save un trusted html from
| your user in your database, then send that untrusted html to your
| users, but in the front end parse it down to just the safe bits?
|
| I think maybe a better api would be to add an unsafe html tag so
| it would look something like: <unsafe>
| all unsafe code here </unsafe>
|
| Then if the browsers do indeed support it, it would work even
| without javascript.
|
| But in any case, you really should be validating everything
| server side.
| jagged-chisel wrote:
| I think this API makes more sense from another standpoint as
| well.
|
| You don't want developers trying to rely on client-only
| sanitization for user input submitted to the server. Sanitizing
| while setting a user-face UI makes sense.
___________________________________________________________________
(page generated 2025-12-10 23:01 UTC)