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:
+9
-1
@@ -78,7 +78,13 @@ const bundles = {
|
||||
|
||||
function updateInlineScriptCspHash() {
|
||||
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 block = match[1] || '';
|
||||
return block.includes("license-topbar-version") || block.includes("openVersionInfo") || block.includes("widget-");
|
||||
@@ -90,6 +96,8 @@ function updateInlineScriptCspHash() {
|
||||
|
||||
const scriptContent = target[1];
|
||||
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(
|
||||
/script-src 'self' 'sha256-[^']+';/,
|
||||
`script-src 'self' 'sha256-${hash}';`
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<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" type="image/png" sizes="192x192" href="/assets/icon-192.png">
|
||||
|
||||
Reference in New Issue
Block a user