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.
>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.
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.
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.
> 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.
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.
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.
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 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.
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
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.
> 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.
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.
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 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.
Isn't this just a XSS vulnerability?
And if you let the attacker inject double quotes, then they can escape the attribute and do something like this:
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.
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.
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.
It doesn’t wash your hands clean of the responsibility to restrict what kinds of content users can inject into served pages.
If they can, why wouldn’t it be inline <script>?
At least thats how I see it. Ideally you'd use something similar to prepared statements, just for html templates.
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 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.
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/
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.
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.
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?
Deleted Comment
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'm surprised that CSPs with unsafe-inline aren't treated with the same hate that table based layouts get. They are really bad.
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.