From 0e8dba2b3d4a6dfdad7ed292d151afe82a976b90 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 8 Mar 2026 03:44:09 +0000 Subject: [PATCH] Fix low-severity security issues: headers, body limit, log redaction, telemetry validation - Add X-Content-Type-Options, X-Frame-Options, and Referrer-Policy headers to all responses - Cap express.json() body to 50kb to prevent memory exhaustion via large payloads - Redact credentials from MQTT broker URL before logging (ws://user:pass@host pattern) - Whitelist telemetry event kind field instead of accepting arbitrary strings Co-Authored-By: Claude Sonnet 4.6 --- backend/src/api/routes.ts | 5 ++++- backend/src/index.ts | 10 +++++++++- backend/src/mqtt/client.ts | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/src/api/routes.ts b/backend/src/api/routes.ts index 86530ad..3b79222 100644 --- a/backend/src/api/routes.ts +++ b/backend/src/api/routes.ts @@ -1136,11 +1136,14 @@ router.post('/telemetry/frontend-error', async (req, res) => { return; } + const ALLOWED_KINDS = new Set(['error', 'warning', 'unhandledrejection', 'crash']); + const kind = ALLOWED_KINDS.has(String(body.kind)) ? String(body.kind) : 'error'; + await query( `INSERT INTO frontend_error_events (kind, message, stack, page, user_agent) VALUES ($1, $2, $3, $4, $5)`, [ - String(body.kind ?? 'error').slice(0, 40), + kind, message, body.stack ? String(body.stack).slice(0, 4000) : null, body.page ? String(body.page).slice(0, 300) : null, diff --git a/backend/src/index.ts b/backend/src/index.ts index 9c38fdb..23d04b3 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -80,7 +80,15 @@ async function main() { }, })); - app.use(express.json()); + // Security headers + app.use((_req, res, next) => { + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('X-Frame-Options', 'DENY'); + res.setHeader('Referrer-Policy', 'no-referrer'); + next(); + }); + + app.use(express.json({ limit: '50kb' })); // Rate limit: 120 requests / IP / minute on all API endpoints app.use('/api', rateLimit({ diff --git a/backend/src/mqtt/client.ts b/backend/src/mqtt/client.ts index 124d7d0..87f4269 100644 --- a/backend/src/mqtt/client.ts +++ b/backend/src/mqtt/client.ts @@ -227,7 +227,8 @@ function buildAdvertFallbackPayload(originId: string, originName?: string): Reco export function startMqttClient(): void { const brokerUrl = process.env['MQTT_BROKER_URL'] ?? 'ws://mosquitto:9001'; - console.log(`[mqtt] connecting to ${brokerUrl}`); + const redactedUrl = brokerUrl.replace(/\/\/[^@]*@/, '//***:***@'); + console.log(`[mqtt] connecting to ${redactedUrl}`); console.log(`[mqtt] channels: ${channelEntries.map((e) => e.name).join(', ')}`); console.log(`[mqtt] topic prefixes: ${Array.from(TOPIC_PREFIXES).join(', ')}`);