From 8a86cf42d4274656c45ee4d490a7ff86b23e3c5f Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Wed, 8 Apr 2026 22:44:19 +0700 Subject: [PATCH] fix(auth): enforce JWT secrets in all environments, not just production validateEnv() previously skipped validation entirely when NODE_ENV !== 'production', allowing the app to start without JWT_SECRET and JWT_REFRESH_SECRET in dev/staging. Split required vars into ALWAYS_REQUIRED (JWT secrets) and REQUIRED_IN_PRODUCTION (infrastructure) so security-critical secrets are validated in every environment. Co-Authored-By: Paperclip --- .../shared/infrastructure/env-validation.ts | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/apps/api/src/modules/shared/infrastructure/env-validation.ts b/apps/api/src/modules/shared/infrastructure/env-validation.ts index f222759..75b5dfb 100644 --- a/apps/api/src/modules/shared/infrastructure/env-validation.ts +++ b/apps/api/src/modules/shared/infrastructure/env-validation.ts @@ -1,11 +1,16 @@ /** - * Validates that critical environment variables are set in production. - * Call this at application startup before any module initialization. + * Validates that critical environment variables are set at application startup. + * + * Security-sensitive vars (JWT secrets) are required in ALL environments. + * Infrastructure vars (DATABASE_URL, CORS, Redis) are required only in production. */ -const REQUIRED_IN_PRODUCTION: readonly string[] = [ +const ALWAYS_REQUIRED: readonly string[] = [ 'JWT_SECRET', 'JWT_REFRESH_SECRET', +]; + +const REQUIRED_IN_PRODUCTION: readonly string[] = [ 'DATABASE_URL', 'CORS_ORIGINS', 'REDIS_HOST', @@ -30,14 +35,11 @@ const REQUIRED_WHEN_USED: ReadonlyMap = new Map([ export function validateEnv(): void { const isProduction = process.env['NODE_ENV'] === 'production'; - - if (!isProduction) { - return; - } - const missing: string[] = []; - for (const key of REQUIRED_IN_PRODUCTION) { + // JWT secrets are required in every environment — a missing secret is a + // security risk regardless of NODE_ENV. + for (const key of ALWAYS_REQUIRED) { if (!process.env[key]) { missing.push(key); } @@ -45,7 +47,26 @@ export function validateEnv(): void { if (missing.length > 0) { throw new Error( - `Missing required environment variables in production:\n ${missing.join('\n ')}\n` + + `Missing required environment variables:\n ${missing.join('\n ')}\n` + + 'JWT_SECRET and JWT_REFRESH_SECRET must always be set. See .env.example.', + ); + } + + if (!isProduction) { + return; + } + + // Infrastructure vars — required in production only. + const missingProd: string[] = []; + for (const key of REQUIRED_IN_PRODUCTION) { + if (!process.env[key]) { + missingProd.push(key); + } + } + + if (missingProd.length > 0) { + throw new Error( + `Missing required environment variables in production:\n ${missingProd.join('\n ')}\n` + 'See .env.example for the full list of variables.', ); }