Hacker News new | past | comments | ask | show | jobs | submit login
Htmx does not play well with content security policy (sjoerdlangkemper.nl)
97 points by Tangiest 10 months ago | hide | past | favorite | 60 comments



There are some good points here, but I think the author neglects to dig deeper and explain how you can mitigate some of these risks with htmx settings.

I've been using htmx with CSP for the past couple of weeks, and I do find that it expands attack surface in an unpleasant way, but I do still think it brings enough value that I'm willing to rely on other protections.

>That cors.php file contains the JavaScript payload, and also sets CORS headers so that the browser has access to it.

The following htmx settings should defeat this:

    htmx.config.selfRequestsOnly = true;
    htmx.config.allowScriptTags = false;
    htmx.config.allowEval = false;
>HTMX has functionality that automatically adds the correct nonce to inline scripts it retrieves. This is convenient, but totally breaks the security model of CSP with nonces.

Agreed, this seems like an anti-feature that completely subverts CSP.

>Of course, this is trivial to bypass: just close the div tag with </div> and insert your payload outside of the element with the hx-disable attribute.

This is true, but it kind of glosses over the fact that you have to screw up a lot more to give the attacker complete control over the HTML. Usually you're not just executing attacker-controlled content as HTML. If you're using a templating system, the more likely vulnerability is that the attacker is limited to escaping an HTML attribute and adding extra one. That said, it's true that if you have a vulnerability that allows an attacker can escape one context, it's much more likely that they can escape them all.

>For these to work, the application has to allow evaluating dynamic code, using the CSP option unsafe-eval. However, allowing unsafe-eval immediately makes it possible to inject JavaScript using HTMX functionality.

I think this is the weakest point, as htmx works well without unsave-eval. I've been using it in my app without unsafe-eval. The features you lose are just convenience features for writing HTML that you can still achieve by subscribing to htmx events in JS. Yes, it means you have to write a little bit more JS, but it's much better than including unsafe-eval.

I wish that htmx would be a bit more CSP-friendly, but it's much better than other similar frameworks.


> you have to screw up a lot more to give the attacker complete control over the HTML.

Isn't this just a XSS vulnerability?


What I mean is that imagine you have HTML like this:

    <div hx-disable><img src="foo.jpg"></div>
You might screw up and give the attacker a way to control the src attribute like:

    <div hx-disable><img src="//evil.com/bad.jpg"></div>
So, maybe unintentional behavior but still no xss.

And if you let the attacker inject double quotes, then they can escape the attribute and do something like this:

    <div hx-disable><img src="foo" hx-delete="/account" hx-trigger="load"></div>
In this case, htmx would still prevent the xss because hx-disable causes htmx to ignore the hx-delete attribute in the child element.

But if we assume the server fails to encode angle brackets too, then the attacker wins because they can terminate the hx-disable, as described in the post.

    <div hx-disable><img src="foo"></div><span hx-delete="/account" hx-trigger="load"></span>
But they're different levels of screwup. Accidentally letting the attacker control the value of the attribute is one level, letting them inject extra attributes is another level, and letting them inject extra HTML elements is another level.

Granted, if you're screwing up output encoding, you're likely going to grant the attacker the ability to inject elements at the same time that you allow them to inject attributes, but the defender has a bit more protection if they disallow or encode angle brackets in user input.


> letting them inject extra attributes is another level, and letting them inject extra HTML elements is another level.

These are ultimately the same problem. "Letting" is the wrong word here—nobody intends to let an XSS happen. These are two scenarios I've encountered in my own work:

- You're doing custom rendering, like markdown, and the output isn't exhaustively sanitized

- You used triple braces in Handlebars (or the unescaped output syntax of your favorite templating language) and the code gets reused for something unrelated that shouldn't be using unescaped output.

"Screwing up output encoding" is an alarmingly common vulnerability. XSS is arguably one of the most (if not the most) common security vulnerability ever. That's not to say it's a problem with HTMX, but the CSP exists specifically for this reason and HTMX essentially acts as an interpreter that can run arbitrary instructions. It's definitionally a way to bypass the CSP in the face of an XSS where a traditional attack would have been otherwise blocked by the CSP.


If you allow random users to write arbitrary unsanitized HTML into other users’ pages, you have already lost, htmx or otherwise.


> If you allow random users to write arbitrary unsanitized HTML into other users’ pages, you have already lost

Not really. The whole point of CSP is that, even if someone manages to inject arbitrary HTML into your page due to a bug or oversight, you can prevent injected scripts from running entirely, severely limiting what an attacker can do.

I'm surprised at the amount of comments on this post that seem to be completely clueless about CSP.


CSP is a backstop that—when configured properly and used alongside restraint in how you script—can minimize some of the worst consequences of injection.

It doesn’t wash your hands clean of the responsibility to restrict what kinds of content users can inject into served pages.


> if someone manages to inject arbitrary HTML

If they can, why wouldn’t it be inline <script>?


Because CSP can be configured to block inline scripts.


The syntax to allow inline scripts is even "unsafe-inline" to emphasize that you are entering the danger zone.


serving html with string concatenation is just like creating sql statements with string concat.

At least thats how I see it. Ideally you'd use something similar to prepared statements, just for html templates.


Besides whether it is a real security risk, this is useful information if you have to work with a security department. In particular, there are corporate environments where certain CSP settings would require approval or are simply not possible.


An article focused on "htmx requires more CSP exceptions than you might be comfortable with" would be great. This is not that article.


HTMX always assumes that the incoming HTML is properly sanitized. If it isn't, the application is already vulnerable.

HTMX triggers do in fact use JavaScript eval(), which will get blocked with a CSP that does not allow it. But you can use standard JS scripts to add events. The same goes for inline CSS.


I don't see the point of this article, it's just common sense. If you plan which parts of the DOM are replaced using HTMX, never trust user input. You'll be fine. I use Golang with HTMX and it's amazingly productive.


Not everyone's as knowledged, hence information should be shared



I think the point is that htmx is in no way special. It's the same old category of vulnerability. It's why we have execvpe. It's why we have mysql_real_escape_string_no_really_we_mean_it_this_time().

I remember a similar point being made about LLM output. Now, I'm anything but a LLM fanboy, but if you pipe unknown text into a system interface that's squarely on you.


I mean that's the point. Reminding people of possible dangers is one very important security mission.


I think the point being made that you're replying to is that it has nothing to do with htmx. It isn't htmx that's not playing well with content security policy.


Using htmx effectively prevents using CSP for security hardening.


it’s unclear what point the post is trying to make. The outlined behaviour is not specific to HTMx per-se. These are security considerations for all server rendered pages.

The “basic” golden rules:

- Only call routes you control

- Always use an auto-escaping template engine

- Only serve user-generated content inside HTML tags

- If you have authentication cookies, set them with Secure, HttpOnly, and SameSite=Lax

https://htmx.org/essays/web-security-basics-with-htmx/


The point of CSP is to lock down your site so that any content (buggy or malicious) can't do damage.

This requires templating engines to separate styles, scripts and event handlers from the html.

> These are security considerations for all server rendered pages.

That's not true. With the right CSP and sandbox rules content can be made inert without sanitizing.

Sanitizing is still a good idea of course, but a rendering engine that is designed with CSP in mind will provide more layers of defense.


What is a rendering engine with CSP in mind?

The server can, at best, set the correct CSP header. It’s a validation performed entirely in the browser. Even the best intended rendering from the server can’t prevent CSP violation attempts when the client is executing some kind of script. That’s why even frameworks like Vue and React need a correctly configured CSP.


Rewrite the inline styles/scripts at build times to separate files, generate the appropriate script/style elements, compute the appropriate CSP directive.

The renderer could rewrite

   <div onclick="foo()">
to

   <div id="handler-Oov1to1o">
   <script integrity="sha256-b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c" src="generated-content/handlers.js"></src>

   // handlers.js:

   document.querySelector("handler-Oov1to1o").addEventListener(() => foo())
   // [...] more handlers here

   // HTTP header

   content-security-policy: script-src sha256-b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c <...>;
   
(the boilerplate can be amortized over many such handlers)

Or at least use 'unsafe-hashes' and compute the hashes for onclick handlers and the like and include those in the CSP.

Separation of style/scripts/content has been encouraged for a long time, I don't know why people are walking it back. But if they insist then templates could also be structured more like a multipart document where those aspects sit in separate parts of the document.

As long as you do any of those when the template renderers are compiled then only scripts whitelisted at buildtime will be executed and scripts injected by dynamic content will get blocked.


Thank you for clarifying, now I understand what you mean. I think we’re on the same page! I like separating assets as they can also have a very different lifecycle than the structure / content of a page.


> These are security considerations for all server rendered pages

Well - "server rendered" to my ears primarily means plain html generated on the server using Perl, Python, PHP, CGI or whatever. I presume you mean "server rendered with some client-side code to handle injecting into the current DOM.

Am I just too old-school or does using "server rendered" to mean fairly specific things seem strange?


You're correct. Server-rendered pages may also have some client-side interactivity using, for example, JavaScript/AJAX.... It doesn't need to be a fancy framework like htmx, but one needs to be mindful about user input and remote content either way. The same "techniques" for securing a web page remain in effect.


It doesn't matter how it's rendered or where it's rendered. If it lets user A put unescaped/filtered/sanitised data on user B's pages, it's a security issue, no matter how the page was rendered. It's one that can be mitigated in various ways, but it's important to take into account.


https://github.com/MichaelWest22/htmx-extensions/tree/main/s...

one great feature of htmx is how easy it is to understand and develop extensions for. I've just implemented this extension which allows safer handling of CSP nonce if this was required by an application. Hopefully this will get accepted as an official htmx extension.

My advice is if your not using inline script tags or the eval feature just keep it simple and disable allowScriptTags and allowEval and set a good CSP header. Also make sure you set a htmx-config meta tag in your page headers to set your config and protect from injected meta tags.

If you need inline scripts in a few places then be aware you need to choose a good templating or auto escaping engine on the backend to protect you and think about user inputs and be careful when using any raw escape override functions. If you have a sensitive application that needs regular external pen-testing then look at things like my safe-nonce extension that gives you another layer of protection to sell.


I was under the impression that most (if not all) browser JS frameworks need hilariously bad CSPs like this, particularly for dynamic styling. Am I mistaken?

I'm surprised that CSPs with unsafe-inline aren't treated with the same hate that table based layouts get. They are really bad.


I feel 2/3 of the comments here are missing the point.

CSP is a mitigation for XSS vulnerabilities. Yeah, if you always sanitize everything in the right way, you won't have XSS vulnerabilities.

CSP was born out of the recognition that people fail to properly sanitize everything, even if they know that this is what they should do. Because it's complicated, and there are so many corner-cases and different ways to have XSS. CSP is a mitigation on top, in case your "we sanitize everything in the right way" goes wrong.

Making the point that you don't need CSP if you don't have XSS vulnerabilities is like saying C is secure as long as you know know how to use it and don't write code with memory safety bugs.


This one surprised me:

https://www.sjoerdlangkemper.nl/2024/06/26/htmx-content-secu...

    <div hx-disable>
        <%= raw(user_content) %>
    </div>
So, I get that `raw` prevents htmx from being used. I get that `<script>` still works.

But I find it scary that if `user_content` is `</div><div>...` that is actually injected, as raw HTML. I would expect that the `<%= raw(user_content) %> only has access to contents of the div it is in itself, and nothing more. But instead I understand that the HTML is injected as text (?) and then re-parsed (?).


This differs for different template engines.

In Angular, for example, the template is parsed into a DOM tree, and then template variables are placed in the correct place. This makes injection really hard. In the above example, it would be impossible to break out of the div.

Other template engines just do a string search/replace, and this makes injection easy. Then it's indeed possible to break out of the div just by injecting </div>.

The example you quoted comes directly from the HTMX docs. They don't specify which template system is used, and I don't immediately recognize the syntax to limit it to a specific template system.


HN really strange, page jumped from front to page 3...

I'm still searching for the holy grail of web development without Javascript or TypeScript, still not found.

Leptos still ahead but still imature, recently with panic! with signal usage. Dioxus today commited improvements to suspension such as placeholders...

egui unusable in mobile, as Android keyboard overlaps the window.

Don't know other alternatives in other languages, but for those who don't want JS it's difficult.



I was confused by the "Try it examples", they all say "HTMX is working correctly", even the XSS one didn't pop up a JS alert.


You can view the source to see what the HTMX code is doing


I wonder how the HTMX creator will respond, seems valid



Okay this whole article is hilarious. I still don’t want to use HTMX but now I will happily not use it, rather than angrily not use it like I do with golang.


I moved from PHP to GO

I brought the hate with me.

In all honestly you're allowed to not like a thing (or golfing). You carry on doing you and leave the go jobs to me ;)


You should try HTMX and Golang together, it's an amazingly productive way to build internal tools. Our JS framework of choice is react, but the time it takes to configure a new project in our mono-repo I could've build the whole tool with HTMX and Go. This isn't to shit on react (or our setup) they are both excellent, it's simply to highlight just how productive you can be with those tools.

It's a "use responsible" sort of tech stack though, but for a lot of things... well... it's fine. We're talking about a world where the alternative might be giving some team excel access directly to a production DB. Which is infinitely stupid x 2, but it still happens if development moves too slow.


It’s just not for me.

HTMX fits into the middle ground: websites that are too dynamic to be made with a SSG or CMS, but not dynamic enough to be a SPA.

I don’t work on enough things that fit into that category to justify learning an entire new framework. I’d rather just write React or even use plain JS.

HTMX obviously can’t be used to build a SPA, so even if it were incredible I would still have to keep my React skills up. I’d also worry that if I build something in HTMX and it increases in scope, I’d end up rewriting it in React or maintaining a bird’s nest. It’s easy for me to tell the difference between webthings that “need to be SSG’d” and “needs React”, if I insert HTMX in the middle then I’d be second guessing if I made the right choice.

As for Golang, again I’m just not the target market for it. It took them so long to decide to add generics. For me generics are a basic feature not a nice to have. I don’t agree with the goals of the language, so there’s no point in me learning it.



Just don’t use the eval features, inline scripts and properly sanitise user input (if any) and you will mitigate all these issues


So, CSP is designed to help against the case where an XSS already happened. So, a lot of this seems like valid criticism, but some parts seem wrong. The "loading malicious fragments" part looks more like the motivating case for using CSP in the first place, rather than an implication of HTMX, but correct me if I'm wrong.

The "unsafe eval" and "nonces for inline scripts" sections seem like valid criticisms of how HTMX works currently. It seems both are possible to disable using the configuration, though (meaning you could remove unsafe-eval from your CSP and htmx would still work except the disabled features).

The "hx-disable" one seems like a badly designed security feature, frankly. BUT, the code that rendered the unsanitized HTML content was not htmx (looked like an erb template), so htmx shouldn't really be blamed for that one.


Lets dig in:

Loading malicious fragments: This is in no way related to htmx and is the same for all web implementations because you don't go get possible script code from random untrusted domains you don't trust or control. Also if he had tried this with the current version of htmx (2.0) he would have not been able to do this as it now defaults to blocking hx requests to external sites and you can only use local relative paths now. So win to htmx on this point.

Unsafe eval: Yeah unsafe eval looks bad but almost every other client side framework also has this same issue and it is very hard to turn this one off on any modern interactive or SPA like website today. Htmx has the option to just ignore the two features that need unsafe eval and implement these features with other more custom solutions unlike most other solutions so I have to chalk this up as a second win for htmx.

Disabling HTMX with hx-disable: I think it fair that hx-disable is not a fool proof security feature and is there to stop silly things and provide some isolation for content to separate it from htmx. But you should probably not allow un-sanitized data in here and expect this to save you.

Nonces for inline scripts: Yeah htmx has some support for nonces and they can protect from inline scripts inserted into the page somehow but they won't protect you from scripts returned from your trusted local server via hx requests by design. Another small win here.

Configuration meta tag: This one is a fair point and the use of malicious meta tags is an interesting attack vector. This may weaken some of the security features built into htmx but if you allow un-sanitized user input to be returned by your server like in his example app there are not many solutions that will come off any better off.

Conclusion: When a site uses HTMX, the attack surface of HTML injection is the same as any other SSR solution. It is possible to limit the risk of XSS by using a content security policy. Security is simple and old school as you just sanitize your inputs which all modern backend stacks now make easy. But it not possible to have all HTMX features and provide 100% security against injection (unless you sanitize all inputs and only link to your own server).


Alpine.js has the same problem, but it's just so darn useful


This article seems to be full of security vulns that the hypothetical htmx user is deliberately injecting into their own page. Sure, if you deliberately make your app vulnerable, it's vulnerable. This isn't, uh, unique to htmx.

For example, sure you can allow htmx on your page to make requests to domains you don't control. This is the proverbial shooting yourself in the foot. If you are allowing this in your site, you have much deeper problems than htmx and need much bigger interventions than CSP.


Not really news. Always sanitize user input html. No matter if you use HTMX or something else.


The relevant thing here is that before, CSP would protect you even if you failed to sanitize an attacker-controlled input. Bringing in htmx allows the attacker to bypass CSP, and that hasn't been documented well before.

The htmx docs mention CSP in passing but don't explore the nuance that OP does.


I think that's only true if your CSP has the word 'unsafe' or '*' in it. If it doesn't, it shouldn't allow inline code or things from domains that you didn't whitelist.

And that's not HTMX or CSP's fault.


>I think that's only true if your CSP has the word 'unsafe' or '*' in it.

No, that's the subtlety that a lot of people in the thread are missing. I wish OP did a better job of explaining the difference.

Imagine that you screw up and some part of your page assigns injects an attacker-controlled string without encoding properly:

So you have something like this:

    <div class="ugc">{{ user_message }}</div>
And the attacker can inject arbitrary HTML/JS, so they do this:

    <div class="ugc"><script>fetch('/account', { method: 'DELETE' })</script></div>
If you have CSP enabled with standard settings, the attack won't execute. CSP saves you even if you screwed up that badly.

The problem is that even if you keep the CSP settings secure and you add htmx into your app, the attacker can effectively achieve XSS because htmx's functionality is powerful enough that it's mostly equivalent to having arbitrary JS execution:

    <div class="ugc"><span hx-delete="/account" hx-trigger="load"></span></div>
So, without htmx, CSP protects you from mistakenly sanitizing input / encoding output, but with htmx, you lose a lot of the benefit of CSP.


Fair enough. It is a trade-off I guess.


Yeah, if HTMX is not protecting against injection of its own code, then it's absolutely at fault here. That's a huge problem.


Sure, but you should always not trust any input, even if you think it's been sanitized.


Checks security headers for web server hosting article and finds a F with no headers set


The headers say "Server: AmazonS3" which means it's a static site. Given that it's also a personal blog, there's not much point in CSP headers because a) it's not processing user input and b) even if an attacker achieves XSS, there's no data to steal or useful malicious actions to take on a static personal blog.


Yeah the risk here is very low unless they start hosting more complicated content later on. But it is not hosted on S3 and is actually hosted by AWS CloudFront with a S3 origin which now has a built in feature to set security headers for your static site in only a few clicks so you can get that sweet sweet A rating ;)




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: