# #region AuthLoggerModule [C:5] [TYPE Module] [SEMANTICS auth, audit, logging] # @defgroup Auth Module group. # # @BRIEF Structured auth logging module for audit trail generation. # @LAYER Core # @RELATION DEPENDS_ON -> [EXT:frontend:core_logger] # # @INVARIANT Must not log sensitive data like passwords or full tokens. # Sensitive field names (password, secret, token, key, authorization) # in the details dict are automatically masked. # @PRE Auth module initialized # @POST Audit logging functions exported # @SIDE_EFFECT Writes auth audit log entries # @DATA_CONTRACT AuthEvent -> LogEntry from datetime import UTC, datetime import re from typing import Any from ..logger import belief_scope, logger # Sensitive field name patterns — values for these keys are masked in logs _SENSITIVE_KEYS = re.compile( r"^(password|secret|token|api_key|api\.key|authorization|auth|credential|credit_card|ssn)$", re.IGNORECASE, ) def _mask_details(details: dict[str, Any] | None) -> dict[str, Any] | None: """Return a copy of details with sensitive field values replaced by '***'.""" if not details: return details masked = {} for key, value in details.items(): if isinstance(key, str) and _SENSITIVE_KEYS.match(key): masked[key] = "***" elif isinstance(value, dict): masked[key] = _mask_details(value) else: masked[key] = value return masked # #region log_security_event [TYPE Function] # @ingroup Auth # @BRIEF Logs a security-related event for audit trails. # @PRE event_type and username are strings. # @POST Security event is written to the application log via %s-formatting to prevent log injection. # Sensitive fields in details dict are masked. # @RELATION DEPENDS_ON -> [logger] # @RATIONALE Uses %s-formatting (not f-strings) to prevent log forging via CRLF injection in # user-controlled fields — see [SEC:H-1]. Sensitive fields in details # dict are masked to prevent credential leakage — see [SEC:L-1]. def log_security_event(event_type: str, username: str, details: dict = None): with belief_scope("log_security_event", f"{event_type}:{username}"): timestamp = datetime.now(UTC).isoformat() logger.info( "[AUDIT][%s][%s] User: %s", timestamp, event_type, username, ) safe_details = _mask_details(details) if safe_details: logger.info( "[AUDIT][%s][%s] Details: %s", timestamp, event_type, safe_details, ) # #endregion log_security_event # #endregion AuthLoggerModule