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:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -14,7 +14,8 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-rate-limit": "^6.7.0"
|
"express-rate-limit": "^6.7.0",
|
||||||
|
"helmet": "^4.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^7.6.0",
|
"concurrently": "^7.6.0",
|
||||||
@@ -847,6 +848,14 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||||
@@ -2256,6 +2265,11 @@
|
|||||||
"function-bind": "^1.1.2"
|
"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": {
|
"http-errors": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-rate-limit": "^6.7.0"
|
"express-rate-limit": "^6.7.0",
|
||||||
|
"helmet": "^4.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^7.6.0",
|
"concurrently": "^7.6.0",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
|
const helmet = require('helmet');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
@@ -58,6 +59,10 @@ const { startPoller, POLL_INTERVAL, POLLING_ENABLED } = require('./utils/poller'
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3001;
|
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;
|
const cookieSecret = process.env.COOKIE_SECRET;
|
||||||
if (!cookieSecret && process.env.NODE_ENV === 'production') {
|
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!');
|
console.error('[Security] COOKIE_SECRET is not set in production — cookies are unsigned and can be tampered with!');
|
||||||
|
|||||||
Reference in New Issue
Block a user