From 2adbed591e7df366ba4458969660e75a33a71447 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Tue, 9 Sep 2025 18:10:44 +0200 Subject: [PATCH] feat: inline CSS and JavaScript into HTML and update CSP for enhanced security --- server.js | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/server.js b/server.js index e8baeb6..0880bac 100644 --- a/server.js +++ b/server.js @@ -67,7 +67,7 @@ function generateCSP() { "default-src 'self'", `script-src ${scriptSrc}`, `style-src ${styleSrc}`, - "img-src 'self' https:", + "img-src 'self' https: data:", "font-src 'self'", "connect-src 'self'", "media-src 'self'", @@ -78,11 +78,9 @@ function generateCSP() { "form-action 'self'", "base-uri 'self'", "manifest-src 'self'", + "upgrade-insecure-requests", ].join("; "); - const hashCount = scriptSources.length + styleSources.length; - console.log(`Generated CSP with ${hashCount} inline content hashes:`); - console.log(`${csp}\n`); return csp; } @@ -421,26 +419,6 @@ function preCompress(buf) { }); } -function injectSRIHashes(html) { - const cssEntry = files.get("/style.css"); - if (cssEntry && cssEntry.sri) { - html = html.replace( - /]*href=["']style\.css["'][^>]*)>/gi, - `` - ); - } - - const jsEntry = files.get("/script.js"); - if (jsEntry && jsEntry.sri) { - html = html.replace( - /]*src=["']script\.js["'][^>]*)>/gi, - `` - ); - } - - return html; -} - function generateSRIHash(content) { return crypto.createHash("sha384").update(content, "utf8").digest("base64"); } @@ -450,11 +428,37 @@ async function loadAndMinify(relPath, minify) { const src = await fs.promises.readFile(abs); const originalContent = src.toString("utf8"); + let processedContent = originalContent; + if (relPath === "index.html") { - extractInlineContent(originalContent); + const cssPath = path.join(ROOT, "style.css"); + if (fs.existsSync(cssPath)) { + const cssContent = await fs.promises.readFile(cssPath, "utf8"); + const minifiedCSS = minifyCSS(cssContent); + + processedContent = processedContent.replace( + /]*href=["']style\.css["'][^>]*>/gi, + `` + ); + } + + const jsPath = path.join(ROOT, "script.js"); + if (fs.existsSync(jsPath)) { + const jsContent = await fs.promises.readFile(jsPath, "utf8"); + const minifiedJS = minifyJS(jsContent); + + processedContent = processedContent.replace( + /]*src=["']script\.js["'][^>]*><\/script>/gi, + `` + ); + } + + extractInlineContent(processedContent); } - const body = minify ? Buffer.from(minify(originalContent), "utf8") : src; + const body = minify + ? Buffer.from(minify(processedContent), "utf8") + : Buffer.from(processedContent, "utf8"); let sriHash = null; if (relPath === "style.css" || relPath === "script.js") { @@ -530,6 +534,11 @@ async function build() { } const cspPolicy = generateCSP(); files.set("__csp__", cspPolicy); + + console.log("CSS and JavaScript have been inlined into HTML automatically"); + console.log( + "CSP policy generated with secure hashes for all inline content" + ); } function setSecurityHeaders(res) {