Background information
This was originally reported as a private security issue, but has been approved for handling in the public queue by the Drupal Security Team.
Problem/Motivation
If Drupal is installed in a /drupal sub-folder, this is not allowed:
<a class="use-ajax" href="/foo">Click me</a>
But this will work:
<a data-hx-get="/foo">HTMX</a>
That's because Drupal.url.isLocal() uses drupalSettings.path.baseUrl but isLocalLink() does not.
We should prevent Drupal from making AJAX requests to another app in another folder.
Steps to reproduce
Proposed resolution
Remaining tasks
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
Comments
Comment #4
deruina commentedOpened an MR. With htmx 4 (after #3635924), Drupal's drupal-assets extension already calls Drupal.url.isLocal() in htmx_config_request, but only to skip adding ajax_page_state. The request was still sent. Now it cancels the request (returning false from the hook cancels it in htmx 4) and reports it with Drupal.throwError(), using the same message as ajax.js.
Cross-origin requests already fail because htmx uses fetch mode same-origin, so the practical change is same-host URLs outside the base path, like /other-app/ when Drupal is in /drupal/.
I checked it in headless Chrome with the real drupal.js, htmx.js and htmx-assets.js and baseUrl /drupal/. Before: hx-get="/other-app/endpoint" and "/drupal/../other-app/x" were both sent and swapped in. After: neither reaches the server, and the local one still works.
Added a Nightwatch case to htmxTest.js. It adds buttons for a local URL, an external URL and, when installed in a sub-folder like on CI, a same-host URL outside Drupal, clicks them, and checks that only the local one fired htmx:before:request. I couldn't run the full Nightwatch suite locally. I ran the same test code in my harness, where it passes with the change and fails without it, so CI will be its first real run.
One behavior change to flag: code that used hx-get with a non-Drupal URL on the same host on purpose is now blocked, same as with ajax.js. drupalSettings.ajaxTrustedUrl isn't checked here; I can add that if it's needed.
Comment #5
smustgrave commentedThis is kinda reading like please refer to https://www.drupal.org/docs/develop/issues/issue-procedures-and-etiquett...
Comment #6
deruina commentedFair point. I missed the AI disclosure here too. It's in the MR description now (more on #3590068). If anything in the change is unclear, I'll explain.
Comment #7
fathershawn