fix(security #12): add helmet security response headers

Adds X-DNS-Prefetch-Control, X-Frame-Options, X-Content-Type-Options,
Referrer-Policy, X-XSS-Protection, HSTS (in prod) and others.
CSP disabled for now as the SPA uses inline scripts/styles; a
nonce/hash-based policy is a future hardening step.
This commit is contained in:
2026-05-16 16:23:47 +01:00
parent 1f41114482
commit b608fa0337
3 changed files with 22 additions and 2 deletions

16
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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!');