fix: raise RuntimeError on missing JWT_SECRET_KEY in non-test environments

Prevents production deployments from silently using a hardcoded default
secret. TESTING=1 still resolves to a built-in test key so the existing
test suite is unaffected. Adds a test that verifies the RuntimeError is
raised when neither JWT_SECRET_KEY nor TESTING is set.
This commit is contained in:
ZdenekSrotyr 2026-04-09 06:54:29 +02:00
parent 0d3ab5060c
commit 4aa97c23d2
2 changed files with 43 additions and 4 deletions

View file

@ -6,12 +6,20 @@ from typing import Optional
import jwt
SECRET_KEY = os.environ.get("JWT_SECRET_KEY", "dev-jwt-secret-change-in-production")
SECRET_KEY = os.environ.get("JWT_SECRET_KEY", "")
if not SECRET_KEY:
if os.environ.get("TESTING", "").lower() in ("1", "true"):
SECRET_KEY = "test-jwt-secret-key-minimum-32-chars!!"
else:
raise RuntimeError(
"JWT_SECRET_KEY environment variable is required. "
"Generate one: python -c \"import secrets; print(secrets.token_hex(32))\""
)
elif len(SECRET_KEY) < 32 and os.environ.get("TESTING", "").lower() not in ("1", "true"):
import warnings as _warnings
if len(SECRET_KEY) < 32 and os.environ.get("TESTING", "").lower() not in ("1", "true"):
_warnings.warn(
f"JWT_SECRET_KEY is {len(SECRET_KEY)} chars — minimum 32 recommended for production",
f"JWT_SECRET_KEY is {len(SECRET_KEY)} chars — minimum 32 recommended",
UserWarning, stacklevel=2,
)

View file

@ -1,6 +1,8 @@
"""Security tests — sandbox escapes, SQL injection, access control."""
import importlib
import os
import sys
import pytest
from fastapi.testclient import TestClient
@ -190,3 +192,32 @@ class TestAuthSecurity:
c, _ = client
resp = c.get("/api/scripts")
assert resp.status_code == 401
# ---- JWT Secret Hardening ----
class TestJwtSecretHardening:
def test_raises_without_jwt_secret_in_non_test_env(self):
"""Module-level code must raise RuntimeError when JWT_SECRET_KEY is absent
and TESTING is not set, preventing accidental production deploys with no secret."""
saved_key = os.environ.pop("JWT_SECRET_KEY", None)
saved_testing = os.environ.pop("TESTING", None)
# Eject any cached module so the re-import re-executes module-level code
sys.modules.pop("app.auth.jwt", None)
try:
with pytest.raises(RuntimeError, match="JWT_SECRET_KEY environment variable is required"):
importlib.import_module("app.auth.jwt")
finally:
# Restore environment before re-importing so the module loads cleanly
if saved_key is not None:
os.environ["JWT_SECRET_KEY"] = saved_key
if saved_testing is not None:
os.environ["TESTING"] = saved_testing
# If neither was set (bare test run), use TESTING flag so reload works
if saved_key is None and saved_testing is None:
os.environ["TESTING"] = "1"
sys.modules.pop("app.auth.jwt", None)
importlib.import_module("app.auth.jwt")
# Clean up the temporary TESTING flag if we added it
if saved_key is None and saved_testing is None:
os.environ.pop("TESTING", None)