CSP tripwire: source-scan test + AGENTS.md rule; drop last native onchange
templates/csp_source_test.go scans .templ sources and fails the build on the three authoring patterns hash-mode CSP refuses silently: native on*= handler attributes (quote and templ brace forms), inline <script> content outside the sanctioned theme_flash_guard.templ, and htmx's eval-dependent bindings (hx-on*, js:-prefixed hx-vals — 'unsafe-eval' is not granted). Verified to fire on all four violation classes via a scratch probe file. The dry run immediately caught a live leftover: unlinked_books.templ's onchange="updateSelectedCount()" — CSP-refused since the hash flip. Removed; the count already updates through the document-level delegated change listener that x-init="setupEventDelegation()" registers. AGENTS.md gains a "CSP discipline" ALWAYS-apply section documenting the hash allow-list, the sanctioned-guard invariant, the replacement patterns, and the tripwire test. Verified: go build/vet, templates+middleware+docs tests green; /unlinked- books loads with zero console violations (no fixture rows to exercise the checkbox; the delegated path is unchanged code).
This commit is contained in:
1 parent
56dbca37d0
commit
4681e98766
4 files changed
+119
-2
No files matched your search
@@ -41,6 +41,38 @@ with heavy interactivity. First-paint section markup is still templ
|
||||
deliberate; do not clone it to new pages, and do not "fix" the dashboard
|
||||
without restoring a templ-partial fade path first.
|
||||
|
||||
## CSP discipline (hash-allow-listed script-src) — ALWAYS apply
|
||||
|
||||
script-src carries a sha256 hash, not `'unsafe-inline'` (and never
|
||||
`'unsafe-eval'` — the Alpine build is `@alpinejs/csp` for exactly this
|
||||
reason; keep it in lockstep with alpinejs). The browser therefore
|
||||
refuses, **silently at build/test time**, two authoring patterns:
|
||||
|
||||
- **No inline `<script>` content outside `theme_flash_guard.templ`.**
|
||||
The guard is the app's only inline script; its bytes are pinned by
|
||||
`middleware.ScriptHashThemeFlashGuard` + the drift test. Editing the
|
||||
guard means recomputing the hash (the failing test prints it); adding
|
||||
a *second* inline script means hashing it too and extending
|
||||
`cspSanctionedInlineScripts` in `templates/csp_source_test.go`.
|
||||
Everything else goes in a bundled `web/src` module.
|
||||
- **No native `on*=` handler attributes** (`onclick=`, `onerror=`,
|
||||
`onsubmit=`, `onchange=`, … — quote AND templ brace forms). Inline
|
||||
handlers never go through the hash path; hash-mode CSP refuses them
|
||||
unconditionally. Use Alpine `@click`/`@change`/… bound to component
|
||||
methods (see the 2026-10 CSP-build conversion commits for the
|
||||
pattern), or delegated listeners for htmx-swapped content.
|
||||
- **No htmx eval-dependent bindings** (`hx-on*`, `js:`-prefixed
|
||||
`hx-vals`) — `'unsafe-eval'` is not granted.
|
||||
|
||||
`templates/csp_source_test.go` is the tripwire for all three (it
|
||||
scans the `.templ` sources; it runs in seconds on host). `style-src
|
||||
'unsafe-inline'` remains by design (htmx-injected styles, Alpine
|
||||
`:style`); `'self'` still trusts the origin not to serve
|
||||
attacker-supplied scripts — don't let upload paths become
|
||||
script-executable. Remember: `templ` does not interpolate `{ expr }`
|
||||
inside quoted attribute values (gotcha #1 below), so dynamic
|
||||
`@click={ "fn('" + id + "')" }` attribute values must BE expressions.
|
||||
|
||||
## Library visibility (family-lens rule) — ALWAYS apply
|
||||
|
||||
Per-user library visibility (`library_visibility` table, default-ALLOW:
|
||||
|
||||
Reference in new issue
Block a user