fix(build): compute CSP hash on LF-normalized index.html

When the build runs on a Windows checkout, fs.readFileSync returns the
file with CRLF intact, and the hash of the inline bootstrap script's
body reflects those CRLFs. The release tarball / git transport / Linux
file system strip CRLF on the publishing host, so the browser sees the
LF-only version and computes a different sha256. CSP then blocks the
script — disabling the version widget, theme switcher, and any other
DOM bindings set up in that inline block.

Normalize CRLF -> LF before computing the hash (the on-disk file keeps
its native line endings; only the hash input is normalized). The CSP
allowlist now matches whatever Caddy actually serves.
This commit is contained in:
Sami
2026-05-17 02:20:36 -07:00
parent 939f95d147
commit 49c8ecfb73
2 changed files with 10 additions and 2 deletions
+9 -1
View File
@@ -78,7 +78,13 @@ const bundles = {
function updateInlineScriptCspHash() { function updateInlineScriptCspHash() {
const html = fs.readFileSync(INDEX_HTML, 'utf8'); const html = fs.readFileSync(INDEX_HTML, 'utf8');
const scripts = [...html.matchAll(/<script>([\s\S]*?)<\/script>/g)]; // The hash MUST match what the browser computes from the served bytes.
// git's text normalization + tar transport strip CRLF on the Linux side,
// so the deployed file is always LF-only — even when the dev copy is CRLF
// (e.g. cloned on Windows). Normalize before hashing so a Windows-built
// index.html produces a CSP allowlist that matches the served LF version.
const normalized = html.replace(/\r\n/g, '\n');
const scripts = [...normalized.matchAll(/<script>([\s\S]*?)<\/script>/g)];
const target = scripts.find(match => { const target = scripts.find(match => {
const block = match[1] || ''; const block = match[1] || '';
return block.includes("license-topbar-version") || block.includes("openVersionInfo") || block.includes("widget-"); return block.includes("license-topbar-version") || block.includes("openVersionInfo") || block.includes("widget-");
@@ -90,6 +96,8 @@ function updateInlineScriptCspHash() {
const scriptContent = target[1]; const scriptContent = target[1];
const hash = crypto.createHash('sha256').update(scriptContent).digest('base64'); const hash = crypto.createHash('sha256').update(scriptContent).digest('base64');
// Write the CSP update back into the original (possibly CRLF) file so we
// don't churn the working copy's line endings just because we read it.
const updatedHtml = html.replace( const updatedHtml = html.replace(
/script-src 'self' 'sha256-[^']+';/, /script-src 'self' 'sha256-[^']+';/,
`script-src 'self' 'sha256-${hash}';` `script-src 'self' 'sha256-${hash}';`
+1 -1
View File
@@ -8,7 +8,7 @@
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" /> <meta http-equiv="Expires" content="0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'sha256-kwm9nLWm/jfIuT8y4i62Xq6mDqe4mRlMZn1tDg+5Zek='; style-src 'self' 'unsafe-inline'; img-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self' https://api.open-meteo.com https://geocoding-api.open-meteo.com; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'sha256-Nv8xzCSztfdYOL663VgPKWQn6v0lnM0ACWxkxpFfcfY='; style-src 'self' 'unsafe-inline'; img-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self' https://api.open-meteo.com https://geocoding-api.open-meteo.com; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'">
<link rel="icon" href="/assets/dashcaddy-favicon.ico" sizes="any"> <link rel="icon" href="/assets/dashcaddy-favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/icon-192.png"> <link rel="icon" type="image/png" sizes="192x192" href="/assets/icon-192.png">