feat: make strict-CSP hx-csp adoption ergonomic#1940
Merged
Conversation
Smooths the rough edges from #1938 for adopting the htmx hx-csp extension under a strict (no unsafe-eval) CSP: - Re-export the extension as `@alltuner/vibetuner/htmx/csp` (mirroring htmx/preload, htmx/sse, htmx/live) so projects enable it with one import and no direct htmx.org devDependency to drift from the framework's pinned version. - Add a `body_attrs` block to the skeleton so projects can stamp a single inherited `hx-nonce:inherited="{{ csp_nonce }}"` on <body> (every htmx element inherits it) without forking the template. - Add an `htmx_config` block positioned just before the bundle script. htmx reads its `<meta name="htmx-config">` when the script evaluates, not at DOMContentLoaded, so the meta (and thus safeEval) must precede the bundle — the head block came too late. This sidesteps the ESM import-ordering trap for `htmx.config.safeEval`. - Update the CSP docs (development guide, llms.txt, llms-full.txt) to the new import path, the body_attrs single-stamp pattern, and the meta-tag safeEval approach. Closes #1938 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
davidpoblador
pushed a commit
that referenced
this pull request
May 30, 2026
🤖 I have created a release *beep* *boop* --- ## [10.20.0](v10.19.0...v10.20.0) (2026-05-30) ### Features * make strict-CSP hx-csp adoption ergonomic ([#1940](#1940)) ([80e4bd4](80e4bd4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #1938. Smooths the four friction points the issue hit when adopting the htmx
hx-cspextension under a strict (nounsafe-eval) CSP.1. Re-export the extension (friction #1 + #2)
vibetuner-js/htmx-csp.js+"./htmx/csp"export, mirroring the existinghtmx/preload,htmx/sse,htmx/live. Projects nowimport "@alltuner/vibetuner/htmx/csp";with no directhtmx.orgdevDependency — which previously had to be pinned by hand and would drift from the framework's htmx the moment it bumped.htmx.registerExtension("hx-csp")lands in the output).2.
body_attrsskeleton block (friction #4)<body>tag had no attribute hook, forcing a full skeleton override just to add the nonce. Added{% block body_attrs %}so projects stamp a single inherited nonce:hx-nonce:inherited="{{ csp_nonce }}". Verified against the htmx 4 source that:inheritedresolveshx-noncefrom ancestors and the extension reads it via the sameattributeValuepath — so one stamp on<body>covers every htmx element.3.
htmx_configskeleton block (friction #3)htmx.config.safeEvalmust be set before the extension'sinit, but ESM hoists imports sohtmx.config.safeEval = trueinconfig.jsruns too late. The htmx-native fix is the<meta name="htmx-config">tag — but htmx reads that meta synchronously when its script evaluates (#initHtmxConfigin the constructor), not at DOMContentLoaded. The skeleton'sheadblock renders after the bundle script, so it would be too late.{% block htmx_config %}positioned just before the bundle script, so a meta placed there is parsed first. Documented:4. Docs
development-guide.md,llms.txt,llms-full.txtupdated to the new import path, thebody_attrssingle-stamp pattern, and thehtmx_config/meta safeEval approach.Testing
test_skeleton_extension_points.py: newTestBodyAttrs(renders inside<body>, empty by default) andtest_htmx_config_block_renders_before_bundle_js(ordering invariant — the whole point of the block).node --checkon the new JS, and a bun build of the re-export.Notes
@alltuner/vibetuner/htmx/cspsubpath (next minor after the just-released 10.19.0).hx-nonceonuser/profile.html.jinjais left as-is — harmless, and it inherits correctly whether or not a project setsbody_attrs.🤖 Generated with Claude Code