Skip to content

feat(htmx): prepare framework templates for hx-nonce extension#1773

Merged
davidpoblador merged 1 commit into
mainfrom
feat/htmx-nonce-extension
May 10, 2026
Merged

feat(htmx): prepare framework templates for hx-nonce extension#1773
davidpoblador merged 1 commit into
mainfrom
feat/htmx-nonce-extension

Conversation

@davidpoblador

Copy link
Copy Markdown
Member

Summary

  • htmx 4.0.0-beta3 ships an hx-nonce extension that gates htmx attribute processing behind the page CSP nonce. Elements without a matching hx-nonce are stripped at init time — defence-in-depth against HTML injection.
  • Vibetuner already enforces a nonce-based CSP via SecurityHeadersMiddleware, so the extension is a natural fit. This PR makes the framework's templates ready to opt in.
  • Stamps hx-nonce="{{ csp_nonce }}" on the framework's single htmx-bearing element (Edit Profile button in user/profile.html.jinja). The attribute is a no-op when the extension is not loaded — non-breaking.
  • Documents the opt-in pattern in development-guide.md under the CSP section, and adds a brief reference in vibetuner-template/.claude/rules/frontend.md.

Why opt-in (not auto-enabled)?

Enabling the extension would require every hx-* element in user templates to also carry hx-nonce. That's a meaningful migration burden for existing projects and shouldn't be flipped silently. We document it; users decide.

Why not auto-inject in middleware?

Considered but rejected for now: regex-injecting hx-nonce over response bodies is fragile, has performance cost, and is hard to audit. If we want this later we can add it as a config flag — but the explicit-attribute approach matches the upstream htmx documentation and keeps templates auditable.

Follow-ups

  • Once style-src 'unsafe-inline' is dropped (separate PR), this becomes part of the broader CSP hardening story.
  • If users widely enable the extension, evaluate adding an opt-in middleware auto-injector later.

Test plan

  • uv run pytest tests/unit/ — 793 passed
  • Pre-commit hooks pass (djlint, rumdl, ruff, etc.)
  • Spot-check rendered docs site for the new CSP section
  • Manually enable the extension in a scaffolded project and verify the Edit Profile button still fires

🤖 Generated with Claude Code

The htmx 4.0.0-beta3 hx-nonce extension gates htmx attribute processing
behind the page CSP nonce, providing defence-in-depth against HTML
injection: even if an attacker injects HTML, the browser will not honour
hx-* attributes on injected elements.

Stamp the framework's only htmx-bearing element (Edit Profile button)
with hx-nonce="{{ csp_nonce }}" so the extension is safe to enable from
day one. The attribute is harmless when the extension is not loaded, so
this change is non-breaking on its own.

Document the opt-in pattern in development-guide.md (under CSP) and add
a brief reference in vibetuner-template/.claude/rules/frontend.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@davidpoblador davidpoblador merged commit 16137e8 into main May 10, 2026
2 checks passed
@davidpoblador davidpoblador deleted the feat/htmx-nonce-extension branch May 10, 2026 08:49
davidpoblador added a commit that referenced this pull request May 10, 2026
#1776)

## Summary

Catches up the LLM-targeted docs (`llms.txt`, `llms-full.txt`) and the
scaffolded `vibetuner-template/.claude/rules/frontend.md` with the htmx
beta3 features that landed in #1771, #1773, and #1774. The project's
`CLAUDE.md` requires every feature PR to update those alongside the docs
site, and I missed them in the original three.

- **`llms.txt`** — extends the CSP bullet with `CSP_STYLE_SRC_STRICT`
and adds an `hx-nonce` opt-in bullet
- **`llms-full.txt`** — adds strict style-src + hx-nonce paragraphs to
the Security Headers section, and a "Beta3 Additions" subsection under
the HTMX migration coverage
- **`vibetuner-template/.claude/rules/frontend.md`** — drops the stale
"`hx-on::` shorthand is broken in alpha8" claim (works in beta1+)

## Test plan

- [x] `rumdl` lint passes (pre-commit)
- [x] No new long-line violations in edited regions
- [ ] Spot-check rendered docs site

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
davidpoblador added a commit that referenced this pull request May 11, 2026
…1779)

Closes #1778.

## Summary

Both htmx-nonce and `CSP_STYLE_SRC_STRICT` were documented in the same
wave that introduced them (#1773, #1774, #1776), but they don't yet work
on a freshly scaffolded project today because the released packages
predate those PRs:

- `@alltuner/vibetuner@10.10.0` on npm pulls `htmx.org@4.0.0-beta2`,
which doesn't ship `dist/ext/hx-nonce.js`. The bundler fails with `Could
not resolve "./node_modules/htmx.org/dist/ext/hx-nonce.js"`.
- `vibetuner==10.10.0` on PyPI predates the `style_src_strict` settings
field. Pydantic's `extra="ignore"` swallows `CSP_STYLE_SRC_STRICT`
silently; the CSP header keeps emitting `'unsafe-inline'`.

Both self-resolve as soon as release-please cuts 10.11.0. Until then,
point readers at the version requirement so they don't burn time
debugging.

This adds:

- An admonition note above each opt-in subsection in
`development-guide.md` calling out the minimum version and the failure
mode users will see.
- The same min-version constraint inline in `llms-full.txt` and
`llms.txt`, since those are LLM-targeted and need to be self-contained.

No framework code changes. Confirmed during the post-merge smoke test
(issue #1778) that with the framework installed editable from `main`,
both opt-ins work end-to-end: strict CSP becomes `style-src 'self'
'nonce-…'`, the bundle build succeeds, and
`htmx:security:strip`/`htmx:security:violation` identifiers all appear
in `bundle.js`.

## Test plan

- [x] `rumdl` lint passes (pre-commit)
- [x] No new long-line violations in edited regions
- [ ] Spot-check rendered docs site (admonitions look reasonable)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
davidpoblador pushed a commit that referenced this pull request May 11, 2026
🤖 I have created a release *beep* *boop*
---


##
[10.11.0](v10.10.0...v10.11.0)
(2026-05-11)


### Features

* **htmx:** prepare framework templates for hx-nonce extension
([#1773](#1773))
([16137e8](16137e8))
* **security:** add CSP_STYLE_SRC_STRICT to drop 'unsafe-inline' from
style-src ([#1774](#1774))
([3f74886](3f74886))


### Bug Fixes

* **deps:** update dependency htmx.org to v4.0.0-beta3
([#1770](#1770))
([d21d883](d21d883))


### Documentation Updates

* gate htmx-nonce + strict style-src behind a min-version note
([#1779](#1779))
([bc6d365](bc6d365))
* **htmx:** document beta3 features and correct migration guide
([#1771](#1771))
([2921dbc](2921dbc))
* put htmx Nonce Protection before Strict style-src
([#1775](#1775))
([d1254c9](d1254c9))
* sync llms.txt, llms-full.txt, and frontend rules with htmx beta3
([#1776](#1776))
([b486470](b486470))

---
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant