CSP: replace 'unsafe-inline' with a content hash for the theme flash guard
script-src now allow-lists the guard by sha256 of its rendered bytes (middleware.ScriptHashThemeFlashGuard) instead of 'unsafe-inline' — the guard is the app's only inline <script>. security_headers_test.go renders templates.ThemeFlashGuard() and fails with the replacement hash if the script text ever drifts. Executing anti-XSS strict CSP surfaced three latent violations the eval-removal sweep couldn't see (hash mode is stricter than eval mode, because inline event handler ATTRIBUTES now count against script-src): 1. admin_archived / admin_library: 15 native onclick=/onsubmit= handlers (modal open/close, folder-browser picker, purge button) converted to Alpine @click/@submit component methods — openCreateLibraryModal, hideModal, pickBrowsePath added to the admin component; purgeArchivedItems promoted from its window global into the component and the (window as any) exposure removed. 2. dashboard/docs: onclick handlers converted — dashboard's event.stopPropagation() → @click.stop; docs' sidebar/search closers → closeDocsSidebar/closeSearchResults component methods. 3. 8 onerror= image-fallback attributes (cover placeholders app-wide + audiobook cover hide) replaced by data-fallback attributes handled by one delegated capture-phase listener in web/src/image_fallback.ts — works for htmx-swapped content without re-binding. Verified: theme guard applies a saved theme on public pages under the hash CSP; 14-page authenticated sweep clean; admin pages clean under the promoted admin; admin-library modal open/close bindings exercised; readers render error-free; go build/vet/middleware-test, tsc, vp check/test (146), build:ts all green.
This commit is contained in:
1 parent
6a598786e9
commit
fb95b6d400
26 files changed
+180
-54
No files matched your search
@@ -26,7 +26,7 @@ templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) {
|
||||
<body x-data="docs" class={ "theme-" + user.Theme + " page-docs font-sans antialiased" }>
|
||||
@DocsTopbar(user)
|
||||
<!-- Mobile drawer backdrop -->
|
||||
<div id="docs-overlay" class="hidden fixed inset-0 z-40 lg:hidden" style="background: var(--surface-overlay);" onclick="document.getElementById('docs-sidebar').classList.add('-translate-x-full'); this.classList.add('hidden')"></div>
|
||||
<div id="docs-overlay" class="hidden fixed inset-0 z-40 lg:hidden" style="background: var(--surface-overlay);" @click="closeDocsSidebar($event)"></div>
|
||||
<div class="flex">
|
||||
@DocsSidebar(nav, currentPath)
|
||||
<main class="flex-1 min-w-0">
|
||||
@@ -243,7 +243,7 @@ templ DocsSearchResults(results []SearchResultItem) {
|
||||
<ul class="py-1 list-none m-0 p-0">
|
||||
for _, r := range results {
|
||||
<li>
|
||||
<a href={ r.URL } class="block px-4 py-2 no-underline transition-colors hover:bg-surface-hover" onclick="this.closest('#docs-search-results').classList.add('hidden')">
|
||||
<a href={ r.URL } class="block px-4 py-2 no-underline transition-colors hover:bg-surface-hover" @click="closeSearchResults($event)">
|
||||
<span class="block text-sm font-medium truncate" style="color: var(--text-primary);">{ r.Title }</span>
|
||||
if r.Category != "" {
|
||||
<span class="block text-xs" style="color: var(--text-secondary);">{ r.Category }</span>
|
||||
|
||||
Reference in new issue
Block a user