From 19cb15caef33c2e83b1bdf12e03cc762ae9de71e Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Mon, 31 Jul 2023 11:31:42 -0600 Subject: [PATCH] security improvements - add the `htmx.config.selfRequestsOnly` option - add the `htmx:validateUrl` event - better security documentation (incomplete, need to finish CORS) --- src/htmx.js | 18 ++++++ test/core/security.js | 47 +++++++++++++- www/content/docs.md | 146 +++++++++++++++++++++++++++++++----------- www/content/events.md | 20 ++++++ 4 files changed, 193 insertions(+), 38 deletions(-) diff --git a/src/htmx.js b/src/htmx.js index 005da3d7..8f592893 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -73,6 +73,7 @@ return (function () { getCacheBusterParam: false, globalViewTransitions: false, methodsThatUseUrlParams: ["get"], + selfRequestsOnly: false }, parseInterval:parseInterval, _:internalEval, @@ -2844,6 +2845,18 @@ return (function () { return arr; } + function verifyPath(elt, path, requestConfig) { + var url = new URL(path, document.location.href); + var hostname = document.location.hostname; + var sameHost = hostname !== url.hostname; + if (htmx.config.selfRequestsOnly) { + if (sameHost) { + return false; + } + } + return triggerEvent(elt, "htmx:validateUrl", mergeObjects({url: url, sameHost: sameHost}, requestConfig)); + } + function issueAjaxRequest(verb, path, elt, event, etc, confirmed) { var resolve = null; var reject = null; @@ -3072,6 +3085,11 @@ return (function () { } } + if (!verifyPath(elt, finalPath, requestConfig)) { + triggerErrorEvent(elt, 'htmx:invalidPath', requestConfig) + return; + }; + xhr.open(verb.toUpperCase(), finalPath, true); xhr.overrideMimeType("text/html"); xhr.withCredentials = requestConfig.withCredentials; diff --git a/test/core/security.js b/test/core/security.js index d28da83f..3c95ddf9 100644 --- a/test/core/security.js +++ b/test/core/security.js @@ -6,7 +6,6 @@ describe("security options", function() { }); afterEach(function() { this.server.restore(); - clearWorkArea(); }); it("can disable a single elt", function(){ @@ -105,4 +104,50 @@ describe("security options", function() { this.server.respond(); btn.innerHTML.should.equal("Clicked a second time"); }) + + it("can make egress cross site requests when htmx.config.selfRequestsOnly is enabled", function(done){ + htmx.logAll() + // should trigger send error, rather than reject + var listener = htmx.on("htmx:sendError", function (){ + htmx.off("htmx:sendError", listener); + done(); + }); + this.server.restore(); // use real xhrs + // will 404, but should respond + var btn = make('') + btn.click(); + }) + + it("can't make egress cross site requests when htmx.config.selfRequestsOnly is enabled", function(done){ + htmx.logAll() + // should trigger send error, rather than reject + htmx.config.selfRequestsOnly = true; + var listener = htmx.on("htmx:invalidPath", function (){ + htmx.config.selfRequestsOnly = false; + htmx.off("htmx:invalidPath", listener); + done(); + }) + this.server.restore(); // use real xhrs + // will 404, but should respond + var btn = make('') + btn.click(); + }) + + it("can cancel egress request based on htmx:validateUrl event", function(done){ + htmx.logAll() + // should trigger send error, rather than reject + var pathVerifier = htmx.on("htmx:validateUrl", function (evt){ + evt.preventDefault(); + htmx.off("htmx:validateUrl", pathVerifier); + }) + var listener = htmx.on("htmx:invalidPath", function (){ + htmx.config.selfRequestsOnly = false; + htmx.off("htmx:invalidPath", listener); + done(); + }) + this.server.restore(); // use real xhrs + // will 404, but should respond + var btn = make('') + btn.click(); + }) }); \ No newline at end of file diff --git a/www/content/docs.md b/www/content/docs.md index 52a461ff..4ca408b0 100644 --- a/www/content/docs.md +++ b/www/content/docs.md @@ -1462,30 +1462,101 @@ to generate a different `ETag` for each content. ## Security -htmx allows you to define logic directly in your DOM. This has a number of advantages, the -largest being [Locality of Behavior](@/essays/locality-of-behaviour.md) making your system -more coherent. +htmx allows you to define logic directly in your DOM. This has a number of advantages, the largest being +[Locality of Behavior](@/essays/locality-of-behaviour.md), making your system more coherent. -One concern with this approach, however, is security. This is especially the case if you are injecting user-created -content into your site without any sort of HTML escaping discipline. +One concern with this approach, however, is security: since htmx increases the expressiveness of HTML, if a malicious +user is able to inject HTML into your application they can leverage this expressiveness. -You should, of course, escape all 3rd party untrusted content that is injected into your site to prevent, among other issues, [XSS attacks](https://en.wikipedia.org/wiki/Cross-site_scripting). Attributes starting with `hx-` and `data-hx`, as well as inline `