From 71b171bf7252e12269044bab1db4245c9415b2be Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 2 Nov 2026 09:31:58 -0500 Subject: [PATCH] fix(csp): allow blob: stylesheets in style-src for the reader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every /readers load logged a CSP violation: foliate-js injects its reader-generated stylesheets as blob: URLs, and style-src only allowed 'self' 'unsafe-inline' — so the sheet was refused and selection/ highlight styling inside the reader rode on nothing. blob: was already granted for img-src, font-src, and frame-src for exactly this reader content; style-src now matches. The content is app-generated at runtime (no cross-origin input), script-src is untouched, and the strict script hash allow-list keeps its guarantee. Pinned by TestThemeFlashGuardHashMatchesCSP (script-src only — the style-src addition doesn't interact) and TestGoSourcesAreCSPSafe. --- internal/middleware/security_headers.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/middleware/security_headers.go b/internal/middleware/security_headers.go index 9a438451..681bc341 100644 --- a/internal/middleware/security_headers.go +++ b/internal/middleware/security_headers.go @@ -41,10 +41,13 @@ func SecurityHeadersMiddleware() echo.MiddlewareFunc { // the component and asserts the hash matches. // // style-src keeps 'unsafe-inline' for htmx-injected styles and - // Alpine :style attribute writes. + // Alpine :style attribute writes, plus blob: for the + // reader-generated stylesheets foliate injects at open time + // (blocked since the CSP migration — every /readers load + // logged a violation; selection/highlight styling rode on it). // 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' "+ScriptHashThemeFlashGuard+"; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data: blob:; frame-src 'self' blob:") + c.Response().Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' "+ScriptHashThemeFlashGuard+"; style-src 'self' 'unsafe-inline' blob:; 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=()")