From e1461d9161e3ad9595a0f662676744a030eae433 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 20 Jul 2026 23:02:11 -0500 Subject: [PATCH] fix(security): add SPA CSP, drop x-powered-by, strengthen dedupe hash Address SonarQube security hotspots on the website: - server/src/app.js: replace `contentSecurityPolicy: false` with a helmet CSP tuned for the built React SPA (script-src 'self'; style-src adds 'unsafe-inline' for React inline styles + the Google Fonts stylesheet; font-src gstatic; img-src allows data:/https: for uploads, embedded body images and BRAND_* assets; connect-src 'self' for REST+SSE). upgrade-insecure-requests is intentionally omitted (TLS terminates at the proxy; keeps local `npm start` over http working). The /api/docs Swagger UI route gets a scoped looser policy (inline script/style) since swagger-ui-express injects an inline bootstrap. - client/vite.config.js: disable the inline module-preload polyfill so code-split builds keep `script-src 'self'` valid (RichTextEditor is a separate chunk). - bot/src/app.js, server/src/internalApp.js: disable x-powered-by on the two internal-only listeners (the public app already strips it via helmet). - shardEvents dedupe key: SHA-1 -> SHA-256 truncated to 40 hex chars (fits the existing CHAR(40) column, no migration; it is a content fingerprint, not a security value). schema.sql comment updated to match. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr --- bot/src/app.js | 3 ++ client/vite.config.js | 4 ++ server/db/schema.sql | 5 +- server/src/app.js | 46 +++++++++++++++++-- server/src/internalApp.js | 3 ++ .../model/shardEvents/shardEvents.model.js | 13 ++++-- 6 files changed, 65 insertions(+), 9 deletions(-) diff --git a/bot/src/app.js b/bot/src/app.js index 1029d90..c69dab3 100644 --- a/bot/src/app.js +++ b/bot/src/app.js @@ -4,6 +4,9 @@ const internalRouter = require('./internal/internal.routes') const app = express() +// Internal-only listener, but don't advertise the stack anyway (defense in depth). +app.disable('x-powered-by') + app.use(express.json()) app.get('/health', (req, res) => res.json({ status: 'ok' })) diff --git a/client/vite.config.js b/client/vite.config.js index 46b418f..38ee91b 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -15,5 +15,9 @@ export default defineConfig({ }, build: { outDir: 'dist', + // Don't inject the inline module-preload polyfill script — modern browsers all + // support modulepreload, and an inline