feat(core): enforce tenant isolation guard outside production
Non-production environments (development/testing) now force the effective tenant to the reserved test tenant "llm_tester" (or a test_-prefixed override) regardless of DEFAULT_USER misconfiguration: - Config.effective_default_user only honors DEFAULT_USER outside production when it is llm_tester or test_-prefixed; anything else is forced to llm_tester (tenant_forced flags the override) - Config refuses startup (validation error) when a non-production environment is explicitly configured with the production tenant jpmschweitzer - get_user() applies the same guard at request-context resolution, so an explicit request for the production tenant in dev/test is forced to llm_tester with a warning log - initialize_application() emits one loud startup log line (tenant_guard_active / tenant_guard_production) stating the effective tenant Unit tests cover the dev/test/prod x default/explicit-user matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+30
-1
@@ -41,6 +41,33 @@ current_conversation: ContextVar[str | None] = ContextVar(
|
||||
)
|
||||
|
||||
|
||||
def _apply_tenant_guard(user: str) -> str:
|
||||
"""
|
||||
Enforce tenant isolation at request-context resolution.
|
||||
|
||||
In non-production environments the production tenant must never be
|
||||
the effective user - a request that explicitly asks for it is forced
|
||||
to the reserved test tenant instead (with a loud log line).
|
||||
"""
|
||||
# Import here to avoid circular dependency
|
||||
from src.core.config import PRODUCTION_TENANT, TEST_TENANT, Environment, config
|
||||
|
||||
if (
|
||||
config.ENVIRONMENT != Environment.PRODUCTION
|
||||
and user == PRODUCTION_TENANT
|
||||
):
|
||||
from src.core.logging_config import get_logger
|
||||
|
||||
get_logger(__name__).warning(
|
||||
"tenant_guard_forced",
|
||||
environment=config.ENVIRONMENT.value,
|
||||
requested_tenant=user,
|
||||
forced_tenant=TEST_TENANT,
|
||||
)
|
||||
return TEST_TENANT
|
||||
return user
|
||||
|
||||
|
||||
def get_user() -> str:
|
||||
"""
|
||||
Get current user from request context.
|
||||
@@ -48,6 +75,8 @@ def get_user() -> str:
|
||||
Returns:
|
||||
User identifier for the current request.
|
||||
Falls back to environment-aware default if not set.
|
||||
In non-production environments the production tenant is never
|
||||
returned - the tenant guard forces the reserved test tenant.
|
||||
|
||||
Example:
|
||||
user = get_user() # "llm_tester" (dev) or "jpmschweitzer" (prod)
|
||||
@@ -55,7 +84,7 @@ def get_user() -> str:
|
||||
user = current_user.get()
|
||||
if user == _USER_NOT_SET:
|
||||
return get_default_user()
|
||||
return user
|
||||
return _apply_tenant_guard(user)
|
||||
|
||||
|
||||
def get_conversation_id() -> str | None:
|
||||
|
||||
Reference in New Issue
Block a user