diff --git a/package-lock.json b/package-lock.json index 03f21d9..f0963f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", - "express-rate-limit": "^6.7.0" + "express-rate-limit": "^6.7.0", + "helmet": "^4.6.0" }, "devDependencies": { "concurrently": "^7.6.0", @@ -847,6 +848,14 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", + "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -2256,6 +2265,11 @@ "function-bind": "^1.1.2" } }, + "helmet": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", + "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==" + }, "http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", diff --git a/package.json b/package.json index 90d4b91..3db372a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", - "express-rate-limit": "^6.7.0" + "express-rate-limit": "^6.7.0", + "helmet": "^4.6.0" }, "devDependencies": { "concurrently": "^7.6.0", diff --git a/server/index.js b/server/index.js index a93508b..fd59923 100644 --- a/server/index.js +++ b/server/index.js @@ -1,6 +1,7 @@ const express = require('express'); const path = require('path'); const cookieParser = require('cookie-parser'); +const helmet = require('helmet'); const fs = require('fs'); require('dotenv').config(); @@ -58,6 +59,10 @@ const { startPoller, POLL_INTERVAL, POLLING_ENABLED } = require('./utils/poller' const app = express(); const PORT = process.env.PORT || 3001; +app.use(helmet({ + contentSecurityPolicy: false // SPA uses inline scripts; CSP requires a nonce/hash strategy +})); + const cookieSecret = process.env.COOKIE_SECRET; if (!cookieSecret && process.env.NODE_ENV === 'production') { console.error('[Security] COOKIE_SECRET is not set in production — cookies are unsigned and can be tampered with!');