diff --git a/frontend/index.html b/frontend/index.html index 94a6eac..7c213a1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -157,7 +157,7 @@ diff --git a/frontend/js/app.js b/frontend/js/app.js index d6def43..679a308 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -304,3 +304,12 @@ if (isAuthenticated()) { } window.addEventListener('hashchange', route); route(); + +// Close-modal buttons (replaces inline onclick handlers — required for CSP). +document.addEventListener('click', (e) => { + const closer = e.target.closest('[data-close-modal]'); + if (!closer) return; + const id = closer.dataset.closeModal; + const modal = document.getElementById(id); + if (modal) modal.style.display = 'none'; +}); diff --git a/server/server.js b/server/server.js index aa20bae..6db4d16 100644 --- a/server/server.js +++ b/server/server.js @@ -29,27 +29,100 @@ if (hasSsl) { server = http.createServer(app); } +// Socket.IO CORS is checked via the same corsOriginCheck function defined below +// (after config is loaded). Hoisted into a closure so we can reference it before +// the function is defined — at first connection time, corsOriginCheck exists. const io = new Server(server, { - cors: { origin: '*' }, + cors: { + origin: (origin, cb) => corsOriginCheck(origin, cb), + credentials: true, + }, maxHttpBufferSize: 10 * 1024 * 1024 // 10MB for screenshot uploads }); // Middleware const helmet = require('helmet'); + +// CSP applies to the dashboard / app pages only. Widget and kiosk renders are +// publicly accessed by devices and intentionally use inline scripts/styles — +// they're served from /api/widgets/:id/render and /api/kiosk/:id/render and +// skip the CSP layer below via path-based opt-out. +// +// scriptSrc 'self' blocks