feat(security): add CSP headers with Helmet

This commit is contained in:
Brad Groux 2026-01-28 12:05:43 -06:00
parent 5aa31115ae
commit 9fcb39d0ca
4 changed files with 28 additions and 11 deletions

View file

@ -1728,3 +1728,4 @@
{"type":"task.status_changed","taskId":"task_20260128_u73mT3","project":"veritas-kanban","status":"in-progress","previousStatus":"todo","id":"evt_EjShEPw7YS3u","timestamp":"2026-01-28T18:04:06.746Z"}
{"type":"task.status_changed","taskId":"task_20260128_VVqEG1","project":"veritas-kanban","status":"in-progress","previousStatus":"todo","id":"evt_X4p02S1uaswP","timestamp":"2026-01-28T18:04:06.932Z"}
{"type":"task.status_changed","taskId":"task_20260128_xu0DY5","project":"veritas-kanban","status":"in-progress","previousStatus":"todo","id":"evt_j4vRMJuwSzya","timestamp":"2026-01-28T18:04:07.737Z"}
{"type":"task.status_changed","taskId":"task_20260128_u73mT3","project":"veritas-kanban","status":"done","previousStatus":"in-progress","id":"evt_SBwP5sBSKJW7","timestamp":"2026-01-28T18:05:24.388Z"}

View file

@ -1,4 +1,26 @@
[
{
"id": "activity_1769623529304_9xemufqe7",
"type": "comment_added",
"taskId": "task_20260128_u73mT3",
"taskTitle": "SECURITY: Remove .env from git and create .env.example",
"details": {
"author": "Veritas",
"preview": "Updated .gitignore with explicit .env.example allo..."
},
"timestamp": "2026-01-28T18:05:29.304Z"
},
{
"id": "activity_1769623524389_x9eyqk318",
"type": "status_changed",
"taskId": "task_20260128_u73mT3",
"taskTitle": "SECURITY: Remove .env from git and create .env.example",
"details": {
"from": "in-progress",
"status": "done"
},
"timestamp": "2026-01-28T18:05:24.389Z"
},
{
"id": "activity_1769623447737_ioe02qo88",
"type": "status_changed",

View file

@ -88,7 +88,7 @@ app.use(
frameSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
...(isDev ? {} : { upgradeInsecureRequests: [] }),
upgradeInsecureRequests: isDev ? null : [],
},
},
// Cross-Origin-Embedder-Policy can break loading of cross-origin resources;
@ -391,6 +391,7 @@ server.listen(PORT, () => {
║ Health: http://localhost:${PORT}/health ║
║ ${authLine.padEnd(42)}║
║ ${corsLine.padEnd(42)}║
║ Helmet: ON (CSP + security headers) ║
║ Rate Limit: 100 req/min ║
║ Body Limit: 1MB ║
╚═══════════════════════════════════════════════╝

View file

@ -154,13 +154,6 @@ function validateApiKey(apiKey: string, config: AuthConfig): { valid: boolean; r
// === JWT Verification ===
function verifyJwtCookie(req: Request): { valid: boolean; error?: string } {
const securityConfig = getSecurityConfig();
// No JWT secret means no password auth configured
if (!securityConfig.jwtSecret) {
return { valid: false };
}
// Get cookie from request
const token = req.cookies?.veritas_session;
if (!token) {
@ -168,7 +161,7 @@ function verifyJwtCookie(req: Request): { valid: boolean; error?: string } {
}
try {
jwt.verify(token, securityConfig.jwtSecret);
jwt.verify(token, getJwtSecret());
return { valid: true };
} catch (err) {
if (err instanceof jwt.TokenExpiredError) {
@ -342,11 +335,11 @@ export function authenticateWebSocket(req: IncomingMessage): WebSocketAuthResult
}
// 1. Check JWT cookie
if (passwordAuthEnabled && securityConfig.jwtSecret) {
if (passwordAuthEnabled) {
const token = extractJwtFromWebSocket(req);
if (token) {
try {
jwt.verify(token, securityConfig.jwtSecret);
jwt.verify(token, getJwtSecret());
return { authenticated: true, role: 'admin', keyName: 'session', isLocalhost };
} catch {
// Token invalid or expired, continue to other auth methods