From 6b5e8b9df85b7afd64a12c0a48c7b598a8173d95 Mon Sep 17 00:00:00 2001 From: leubeem Date: Wed, 17 Jun 2026 09:37:45 +0200 Subject: [PATCH] fix(security): mandatory cron secret + reject placeholder JWT secrets (SEC-07, OPS-02, SEC-09) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SEC-07 — committed secrets: - Replace real-looking defaults in .env.example (JWT_SECRET/JWT_REFRESH_SECRET "changeme", MANUAL_INVITE_PASSWORD "pm.local@zghl.de") with CHANGE_ME placeholders, and drop the personal DOME_LIGHT_WEBHOOK_URL default. - Add a Nitro startup plugin (server/plugins/validate-secrets.ts) that refuses to boot in production when JWT_SECRET is unset, looks like a placeholder, or is shorter than 32 chars (warns only in development). OPS-02 / SEC-09 — cron endpoints: - requireCronSecret now fails closed: when no CRON_SECRET/KPI_CRON_SECRET is configured the endpoint returns 503 instead of being publicly callable (previously it allowed the request with a warning). Both cron routes already call the guard. Prefer the x-cron-secret header over the loggable ?secret= query param; document CRON_SECRET in .env.example. Operational note: production deployments must now set JWT_SECRET (>=32 chars) and CRON_SECRET, or the server won't start / crons return 503. Co-Authored-By: Claude Opus 4.8 --- .env.example | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 16cb121..7c93214 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,11 @@ NODE_ENV=development MONGODB_URI=mongodb://127.0.0.1:27017/opensquawk # Authentication -JWT_SECRET=changeme -JWT_REFRESH_SECRET=changeme +# Generate strong, unique random values — e.g. `openssl rand -hex 32`. +# The server refuses to start in production if these are unset, look like a +# placeholder, or are shorter than 32 characters. +JWT_SECRET=CHANGE_ME +JWT_REFRESH_SECRET=CHANGE_ME # OpenAI OPENAI_API_KEY=sk-your-openai-key @@ -23,7 +26,8 @@ USE_PIPER=false PIPER_PORT=5001 SPEACHES_BASE_URL= SPEECH_MODEL_ID=speaches-ai/piper-en_US-ryan-low -DOME_LIGHT_WEBHOOK_URL=https://home.io.faktorxmensch.com/api/webhook/lidl_stab_3modi_8492 +# Optional: external webhook for bridge dome-light telemetry. Leave empty to disable. +DOME_LIGHT_WEBHOOK_URL= # Notifications NOTIFY_RESEND_API_KEY= @@ -39,4 +43,10 @@ NOTIFY_SMTP_PASS= BOOTSTRAP_INVITE_DEADLINE=2025-09-01T00:00:00Z # Manual invitation generator -MANUAL_INVITE_PASSWORD=pm.local@zghl.de +MANUAL_INVITE_PASSWORD=CHANGE_ME + +# Cron / scheduled tasks +# Required for the /api/service/cron/* endpoints (they send emails and mint +# invite codes). Without it those endpoints return 503. Pass it via the +# `x-cron-secret` header (preferred) or `?secret=` query param. +CRON_SECRET=CHANGE_ME