July 2nd, 2024

Htmx does not play well with content security policy

HTMX, a JavaScript framework, presents security challenges due to its handling of HTML tags and external script loading. Despite some security features, HTMX usage raises HTML injection risks, complicating full security implementation.

Read original articleLink Icon
Htmx does not play well with content security policy

HTMX, a JavaScript framework for dynamic data loading, poses challenges with Content Security Policy (CSP) and cross-site scripting (XSS) protection. HTMX's use of normal HTML tags with custom attributes makes it hard to secure against XSS attacks. Malicious code injection is a concern, as HTMX can load scripts from external sources and execute them on the page. Features like unsafe eval and configuration meta tags further complicate security measures, potentially enabling attackers to bypass CSP restrictions. While HTMX offers some security features like hx-disable and nonce support, these can be circumvented, undermining CSP protections. Overall, using HTMX increases the risk of HTML injection attacks, and achieving full security while leveraging its features remains a challenge.

Link Icon 22 comments
By @mtlynch - 5 months
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.

By @stouset - 5 months
If you allow random users to write arbitrary unsanitized HTML into other users’ pages, you have already lost, htmx or otherwise.
By @Ayesh - 5 months
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.

By @ackers - 5 months
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.
By @iamkonstantin - 5 months
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/

By @latent22 - 4 months
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.

By @theandrewbailey - 5 months
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.

By @hannob - 5 months
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.

By @OptionOfT - 5 months
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 (?).

By @osmano807 - 5 months
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.

By @recursivedoubts - 5 months
By @nop_slide - 5 months
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.
By @hshsh667 - 5 months
I wonder how the HTMX creator will respond, seems valid
By @ryze20245 - 5 months
Just don’t use the eval features, inline scripts and properly sanitise user input (if any) and you will mitigate all these issues
By @CGamesPlay - 5 months
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.

By @latent22 - 5 months
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).

By @foreigner - 5 months
Alpine.js has the same problem, but it's just so darn useful
By @yawaramin - 5 months
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.

By @victorbjorklund - 5 months
Not really news. Always sanitize user input html. No matter if you use HTMX or something else.
By @latent22 - 5 months
Checks security headers for web server hosting article and finds a F with no headers set