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(', ')}`);