53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
"""Fail-fast checks for insecure production defaults."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from urllib.parse import urlparse
|
|
|
|
from app.config import Settings
|
|
|
|
logger = logging.getLogger("sac")
|
|
|
|
_WEAK_JWT_SECRETS = frozenset(
|
|
{
|
|
"",
|
|
"change-me-in-production",
|
|
"change-me-openssl-rand-hex-32",
|
|
"CHANGE_ME_openssl_rand_hex_32",
|
|
}
|
|
)
|
|
|
|
|
|
def _is_local_dev_url(public_url: str) -> bool:
|
|
parsed = urlparse(public_url.strip() or "http://localhost:8000")
|
|
host = (parsed.hostname or "").lower()
|
|
return host in {"localhost", "127.0.0.1", "::1", "testserver"}
|
|
|
|
|
|
def validate_security_settings(settings: Settings) -> None:
|
|
"""Raise on dangerous defaults when SAC_SECURITY_ENFORCE=true."""
|
|
if not settings.sac_security_enforce:
|
|
return
|
|
|
|
if settings.jwt_secret in _WEAK_JWT_SECRETS:
|
|
raise RuntimeError(
|
|
"JWT_SECRET is missing or uses an insecure default. "
|
|
"Set a random value in sac-api.env (openssl rand -hex 32)."
|
|
)
|
|
|
|
if settings.cors_origins.strip() == "*" and not _is_local_dev_url(settings.sac_public_url):
|
|
raise RuntimeError(
|
|
"CORS_ORIGINS=* is not allowed with SAC_SECURITY_ENFORCE=true on non-local SAC_PUBLIC_URL. "
|
|
"Set CORS_ORIGINS to your UI origin, e.g. https://sac.example.com"
|
|
)
|
|
|
|
|
|
def warn_relaxed_security(settings: Settings) -> None:
|
|
if settings.sac_security_enforce:
|
|
return
|
|
if settings.jwt_secret in _WEAK_JWT_SECRETS:
|
|
logger.warning("JWT_SECRET uses insecure default — set a strong secret before production")
|
|
if settings.cors_origins.strip() == "*":
|
|
logger.warning("CORS_ORIGINS=* — restrict to explicit origins in production")
|