fix: #3621557 Put a real CSRF token in the HTMX reset URL instead of the placeholder

Related to [#3621557].

Problem

Clicking Reset Canvas layout never resets anything. The POST is refused with 'csrf_token' URL query argument is invalid and the watchdog records an access denied, on Drupal 11.4 with Canvas 1.11.

The hx-post URL carries Crypt::hashBase64($path) — the CSRF placeholder, a hash of the path — instead of a token. Being a hash of the path it is byte-identical for every user on every site, so the failure reads as a stale token rather than a missing one.

Cause

hook_menu_local_tasks_alter() generated the URL with Url::toString() on the assumption that omitting $collect_bubbleable_metadata makes RouteProcessorCsrf emit a real token. It does not: the url_generator service is MetadataBubblingUrlGenerator, which always generates with bubbleable metadata and bubbles it into the active render context, whatever the caller asks for. The route processor therefore always takes its placeholder branch.

The hook runs with no render context, so that bubbling goes nowhere and the lazy builder that would replace the placeholder is never registered. The placeholder reaches the browser verbatim.

Proposed resolution

Swap the placeholder the generator produced for the token core would have rendered into it, using the injected csrf_token service and the route's own internal path. The session cache context a real token requires was already being added.

The no-JavaScript fallback (the standalone confirm form) was unaffected and still works.

Verification

Plain Drupal 11.4.6 + Canvas 1.11.0, Canvas Override enabled on Article:

  • before: hx-post token equals Crypt::hashBase64('node/1/canvas/reset/confirm'), POST → 403
  • after: real per-session token, POST → 204 with HX-Redirect, layout cleared, status message shown, new revision recorded
  • no-JS confirm form: still resets and records a revision
  • 18/18 unit tests pass, no new phpcs findings

Checkpoints

  • Reproduced the problem
  • Fixed the problem
  • Verified manually in a browser
  • Automated test coverage
  • Followed the Drupal coding standards
  • Updated documentation where needed

This change was produced with AI assistance (Claude) and reviewed and tested by a human before submission, in line with the Drupal AI policy.

Merge request reports

Loading
Loading