CRITICAL (CVE-class): - C-4: Remove DEV_MODE fallback with hardcoded postgres:postgres credentials - C-3: WebSocket endpoints now require JWT or API key token (?token=) auth - C-1: Superset environment passwords encrypted via Fernet at rest in DB - H-4: SESSION_SECRET_KEY separated from JWT AUTH_SECRET_KEY - H-1: Log injection via %s-formatting instead of f-strings - H-5: X-Trace-ID header validated as UUID4 to prevent trace poisoning INFRASTRUCTURE: - New src/core/encryption.py — EncryptionManager extracted from llm_provider - Test DB: per-module sqlite:///:memory: with PRAGMA foreign_keys=ON - testcontainers PostgreSQL via TEST_DB=postgres env var - conftest temp-file global engine (no 10GB shared-cache leak) TEST FIXES (44 pre-existing → 0): - test_smoke_plugins: module-level sys.modules mock isolated to per-test fixture - test_migration_engine: EXT:Python:uuid → uuid syntax fix - test_dashboards_api: mock env attributes + correct patch target - test_constants_audit_fixes: sync expected constant names with actual - test_defensive_guards: patch.object instead of module-level Repo mock - test_clean_release_cli: removed empty config.json directory - FK violations: TaskRecord parents in log_persistence, Environment in mapping_service, ReleaseCandidate in candidate_manifest_services ORTHOGONAL TESTS (18 new): - test_security_orthogonal.py: bcrypt 72-byte limit, unicode, API key format invariants, Fernet robustness, encryption key lifecycle, log injection protection, JWT edge cases MODEL FIXES: - MetricSnapshot.job_id: nullable with SET NULL (was broken FK for aggregate prune snapshots, hidden by SQLite) CLEANUP: - Removed stale :memory:test_main/test_auth/test_tasks file databases - Removed duplicate #endregion in encryption_key.py
83 lines
3.3 KiB
Python
83 lines
3.3 KiB
Python
# #region TraceContextMiddlewareModule [C:3] [TYPE Module] [SEMANTICS fastapi, middleware, trace, context, request]
|
|
# @BRIEF FastAPI/Starlette middleware that seeds a trace_id for every incoming HTTP request.
|
|
# Optionally extracts X-Trace-ID header for cross-service trace propagation.
|
|
# @LAYER Core
|
|
# @RELATION DEPENDS_ON -> [CotLoggerModule]
|
|
# @RELATION CALLED_BY -> [AppModule]
|
|
# @INVARIANT X-Trace-ID header is validated as UUID4 format to prevent log injection / trace poisoning.
|
|
# @PRE FastAPI app instance with Starlette middleware support.
|
|
# @POST Every request gets a trace_id via seed_trace_id(). Existing X-Trace-ID header is
|
|
# preserved and used as the trace_id when present and valid.
|
|
# @SIDE_EFFECT Sets ContextVar _trace_id for the duration of the request.
|
|
|
|
import uuid
|
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response
|
|
from starlette.types import ASGIApp
|
|
|
|
from ..cot_logger import seed_trace_id, set_trace_id
|
|
|
|
|
|
# #region TraceContextMiddleware [C:2] [TYPE Class] [SEMANTICS middleware, trace, context, dispatch]
|
|
# @BRIEF Starlette BaseHTTPMiddleware that seeds a trace_id per request.
|
|
class TraceContextMiddleware(BaseHTTPMiddleware):
|
|
"""FastAPI/Starlette middleware that seeds a trace_id for every request.
|
|
|
|
If the incoming request carries an ``X-Trace-ID`` header, that value is
|
|
validated as UUID4 and used as the trace_id (enabling cross-service trace
|
|
chaining). Invalid or non-UUID values are ignored and a new UUID4 is
|
|
generated instead.
|
|
|
|
Usage::
|
|
|
|
from backend.src.core.middleware.trace import TraceContextMiddleware
|
|
app.add_middleware(TraceContextMiddleware)
|
|
"""
|
|
|
|
# #region TraceContextMiddleware.__init__ [TYPE Function]
|
|
# @BRIEF Standard BaseHTTPMiddleware initialiser.
|
|
def __init__(self, app: ASGIApp) -> None:
|
|
super().__init__(app)
|
|
|
|
# #endregion TraceContextMiddleware.__init__
|
|
|
|
# #region TraceContextMiddleware.dispatch [C:2] [TYPE Function] [SEMANTICS dispatch, trace, seed, header, call_next]
|
|
# @BRIEF Dispatch handler that seeds trace_id before passing to the next middleware.
|
|
async def dispatch(
|
|
self, request: Request, call_next
|
|
) -> Response:
|
|
"""Intercept every request, seed the trace_id, and forward.
|
|
|
|
If the client sent an ``X-Trace-ID`` header with a valid UUID4 value,
|
|
use it; otherwise ``seed_trace_id()`` generates a new UUID4.
|
|
Invalid X-Trace-ID values are silently ignored to prevent log
|
|
injection / trace poisoning (see [SEC:H-5]).
|
|
|
|
Args:
|
|
request: The incoming Starlette Request.
|
|
call_next: The next middleware or route handler.
|
|
|
|
Returns:
|
|
Response from the downstream handler.
|
|
"""
|
|
incoming_trace_id = request.headers.get("X-Trace-ID")
|
|
if incoming_trace_id:
|
|
try:
|
|
uuid.UUID(hex=incoming_trace_id, version=4)
|
|
set_trace_id(incoming_trace_id)
|
|
except (ValueError, AttributeError):
|
|
seed_trace_id()
|
|
else:
|
|
seed_trace_id()
|
|
|
|
response = await call_next(request)
|
|
return response
|
|
|
|
# #endregion TraceContextMiddleware.dispatch
|
|
|
|
|
|
# #endregion TraceContextMiddleware
|
|
# #endregion TraceContextMiddlewareModule
|