Files
bookhoard/internal/middleware/security_headers.go
T
John O'Keefe 6d01bd7971 fix(web): convert remaining JS-global bindings for the CSP build; drop unsafe-eval
The strict CSP sweep surfaced a final class of bindings on the audio
reader and book detail pages that referenced browser globals
(Number, String, Math, window, document) — identifiers the CSP-safe
evaluator resolves only from component scope:

- audio reader: scrub input, volume slider/input, speed+skip active
  styles, genre tick styles, and the default-speed select now route
  through component methods (onScrubInput, setVolumeFromEvent,
  applyDefaultSpeedFromSelect, volumeLabel, railTickStyle,
  isSkipActive, isSpeedActive, isDefaultSpeed)
- book detail: cover-upload click and banner edit validation moved to
  component methods (triggerCoverUpload, bannerEditHasText)
- admin: Refresh-to-View-Books reload moved to reloadPage()
- reader: note edit validation moved to noteEditHasText()

CSP header: frame-src 'self' blob: (foliate renders book content in
a blob: frame) and blob: on font-src/img-src (EPUB-embedded fonts
and images load as blob: URLs inside that frame). 'unsafe-eval' is
gone: script-src is 'self' plus 'unsafe-inline' for the pre-paint
theme flash guard.

Also: header openPanel promoted to the root component (one open
panel across all sidebar panels, persisted in init), with the
sidebar shell reduced to themeMode state + methods; fmtTime restored
to the audioReader component (ripped by the unexport pass — time
displays were dead on the audio reader).
2026-10-25 17:39:30 -04:00

30 lines
1.4 KiB
Go

package middleware
import (
"github.com/labstack/echo/v5"
)
// SecurityHeadersMiddleware adds security headers to all responses
func SecurityHeadersMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
// Add security headers
c.Response().Header().Set("X-Content-Type-Options", "nosniff")
c.Response().Header().Set("X-Frame-Options", "DENY")
c.Response().Header().Set("X-XSS-Protection", "1; mode=block")
c.Response().Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
// script-src notes: 'unsafe-inline' is required by the pre-paint
// theme flash guard (inline <script>). Alpine runs via its
// CSP-safe build (@alpinejs/csp), which needs no eval — do NOT
// add 'unsafe-eval' here without removing that build.
// font/img blob: — the reader renders EPUB-embedded fonts and
// images from blob: URLs inside the foliate content frame.
c.Response().Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data: blob:; frame-src 'self' blob:")
c.Response().Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
c.Response().Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
return next(c)
}
}
}