From 5624eead82b39ce50516f2fb38bc9e1652b8fb45 Mon Sep 17 00:00:00 2001 From: fretuks Date: Wed, 7 Oct 2026 23:51:32 +0200 Subject: [PATCH] Update CSP to allow external fonts, scripts, and add corresponding test coverage --- public/chat.html | 2 +- public/js/script.js | 2 +- src/app.js | 2 +- test/app.test.js | 10 ++++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/public/chat.html b/public/chat.html index bd4f1de..2e95a44 100644 --- a/public/chat.html +++ b/public/chat.html @@ -439,7 +439,7 @@ integrity="sha512-rCQgmUulW6f6QegOvTntKKb5IAoxTpGVCdWqYjkXEpzAns6XUFs8NKVqWe+KQpctp/EoRSFSuykVputqknLYMg==" crossorigin="anonymous" defer> - + diff --git a/public/js/script.js b/public/js/script.js index 504b261..c2dc231 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1017,7 +1017,7 @@ if (headerModelButton) headerModelButton.addEventListener("click", (event) => { }); saveGenerationSettingsBtn.addEventListener("click", () => { void saveGenerationSettings(); }); roleplayNewPersonaBtn.addEventListener("click", closeRoleplayStarter); -clearUserPersonaBtn.addEventListener("click", () => { +if (clearUserPersonaBtn) clearUserPersonaBtn.addEventListener("click", () => { closeChatDrawer(); void clearChatUserPersona(); }); diff --git a/src/app.js b/src/app.js index 0efb43e..b6ba751 100644 --- a/src/app.js +++ b/src/app.js @@ -221,7 +221,7 @@ function sameOriginProtection(config) { function browserSecurityHeaders(config) { return (_req, res, next) => { - res.setHeader("Content-Security-Policy", "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' data:; worker-src 'self' blob:"); + res.setHeader("Content-Security-Policy", "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; font-src 'self' data: https://fonts.gstatic.com; worker-src 'self' blob:"); res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=(), usb=()"); res.setHeader("X-Content-Type-Options", "nosniff"); diff --git a/test/app.test.js b/test/app.test.js index 4c05933..869a991 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -127,6 +127,16 @@ test("public health checks reach the backend without exposing diagnostics or req assert.equal((await request(server.baseUrl, "/admin/diagnostics")).status, 403); }); +test("CSP permits the pinned chat markdown script and the app's hosted fonts", async t => { + const server = await startTestServer(); + t.after(() => server.close()); + const response = await fetch(`${server.baseUrl}/`); + const policy = response.headers.get("content-security-policy"); + assert.match(policy, /style-src[^;]*https:\/\/fonts\.googleapis\.com/); + assert.match(policy, /script-src[^;]*https:\/\/cdnjs\.cloudflare\.com/); + assert.match(policy, /font-src[^;]*https:\/\/fonts\.gstatic\.com/); +}); + test("personality API keeps prompts private, validates IDs, and enforces chat ownership", async t => { const server = await startTestServer(); t.after(() => server.close());