@@ -298,14 +298,47 @@ def set_security_headers(response):
298298# Transfer rate limiting configuration to Flask app config
299299app .config ["RATE_LIMITING" ] = SETTINGS .get ("RATE_LIMITING" , {})
300300
301- # Ensure JWT_SECRET_KEY is set with proper fallback
301+ # Ensure JWT_SECRET_KEY is set with proper fallback and validation
302302jwt_secret = (
303303 SETTINGS .get ("JWT_SECRET_KEY" )
304304 or SETTINGS .get ("SECRET_KEY" )
305305 or os .getenv ("JWT_SECRET_KEY" )
306306 or os .getenv ("SECRET_KEY" )
307307)
308308
309+ # Security: Validate JWT secret key to prevent insecure configurations
310+ # This list includes actual defaults from .env.example and develop/test configs
311+ INSECURE_KEY_PATTERNS = [
312+ "your_jwt_secret_key_here" ,
313+ "your_secret_key_here" ,
314+ "develop_jwt_secret" ,
315+ "develop_secret_key" ,
316+ "test_jwt_secret_key" ,
317+ "test_secret_key" ,
318+ ]
319+
320+ if not jwt_secret :
321+ if os .getenv ("ENVIRONMENT" ) == "prod" :
322+ raise RuntimeError (
323+ "CRITICAL SECURITY ERROR: JWT_SECRET_KEY must be set in production. "
324+ "Set the JWT_SECRET_KEY or SECRET_KEY environment variable."
325+ )
326+ logger .warning (
327+ "JWT_SECRET_KEY is not set. This is insecure and must be fixed before "
328+ "deploying to production. Set JWT_SECRET_KEY or SECRET_KEY."
329+ )
330+ elif jwt_secret .lower () in INSECURE_KEY_PATTERNS or len (jwt_secret ) < 32 :
331+ if os .getenv ("ENVIRONMENT" ) == "prod" :
332+ raise RuntimeError (
333+ "CRITICAL SECURITY ERROR: JWT_SECRET_KEY is insecure (too short or "
334+ "uses a known default value). Use a cryptographically secure random "
335+ "string of at least 32 characters."
336+ )
337+ logger .warning (
338+ "JWT_SECRET_KEY appears insecure (too short or uses a default value). "
339+ "This must be fixed before deploying to production."
340+ )
341+
309342app .config ["JWT_SECRET_KEY" ] = jwt_secret
310343app .config ["JWT_ACCESS_TOKEN_EXPIRES" ] = SETTINGS .get ("JWT_ACCESS_TOKEN_EXPIRES" )
311344app .config ["JWT_TOKEN_LOCATION" ] = SETTINGS .get ("JWT_TOKEN_LOCATION" )
@@ -431,13 +464,21 @@ def ping():
431464
432465@app .route ("/debug/routes" , methods = ["GET" ])
433466def debug_routes ():
434- """Debug endpoint to show all registered routes"""
467+ """Debug endpoint to show all registered routes.
468+
469+ Security: This endpoint is only available in development/test environments.
470+ It is completely disabled in production and staging unless explicitly enabled.
471+ """
435472 import os
436473
437- if (
438- os .getenv ("ENVIRONMENT" ) == "prod"
439- and os .getenv ("DEBUG_ROUTES_ENABLED" , "false" ).lower () != "true"
440- ):
474+ environment = os .getenv ("ENVIRONMENT" , "dev" )
475+ debug_routes_enabled = os .getenv ("DEBUG_ROUTES_ENABLED" , "false" ).lower () == "true"
476+
477+ # Security: Only allow in dev/test environments, or if explicitly enabled
478+ if environment not in ("dev" , "test" ) and not debug_routes_enabled :
479+ logger .warning (
480+ f"[SECURITY]: Blocked debug/routes access in { environment } environment"
481+ )
441482 abort (404 )
442483
443484 routes_info = []
0 commit comments