From 8b2388f5fcfc13c31cea180adb280b82581da8e4 Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Wed, 4 Mar 2026 13:06:45 +0700 Subject: [PATCH] feat: Enhance security by logging specific violation details internally and providing generic client error messages, while refining user context in logs. --- src/logging.py | 2 -- src/main.py | 9 +++----- src/middleware.py | 57 +++++++++++++++++++++++++++++++---------------- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/logging.py b/src/logging.py index 22a954a..04adbc0 100644 --- a/src/logging.py +++ b/src/logging.py @@ -60,8 +60,6 @@ class JSONFormatter(logging.Formatter): # Add Context information if available if request_id: log_record["request_id"] = request_id - if role: - log_record["role"] = role # Add Error context if available if hasattr(record, "error_id"): diff --git a/src/main.py b/src/main.py index 0126a57..984b127 100644 --- a/src/main.py +++ b/src/main.py @@ -104,10 +104,8 @@ async def db_session_middleware(request: Request, call_next): set_role(role) user_info_str = "" - if username: - user_info_str = f" | User: {username}" - if role: - user_info_str += f" ({role})" + if user_id: + user_info_str = f" | User ID: {user_id}" error_id = getattr(request.state, "error_id", None) log_msg = f"HTTP {request.method} {request.url.path} completed in {round(process_time, 2)}ms{user_info_str}" @@ -118,11 +116,10 @@ async def db_session_middleware(request: Request, call_next): log_msg, extra={ "method": request.method, - "path": request.url.path, + "path": request.path, "status_code": response.status_code, "duration_ms": round(process_time, 2), "user_id": user_id, - "role": role, "error_id": error_id, }, ) diff --git a/src/middleware.py b/src/middleware.py index 5857074..11f3aba 100644 --- a/src/middleware.py +++ b/src/middleware.py @@ -161,29 +161,36 @@ def inspect_value(value: str, source: str): return if XSS_PATTERN.search(value): - raise HTTPException(status_code=422, detail=f"Potential XSS payload detected in {source}") + log.warning(f"Security violation: Potential XSS payload detected in {source}, value: {value}") + raise HTTPException(status_code=422, detail="Invalid request parameters") if SQLI_PATTERN.search(value): - raise HTTPException(status_code=422, detail=f"Potential SQL injection payload detected in {source}") + log.warning(f"Security violation: Potential SQL injection payload detected in {source}, value: {value}") + raise HTTPException(status_code=422, detail="Invalid request parameters") if RCE_PATTERN.search(value): - raise HTTPException(status_code=422, detail=f"Potential RCE payload detected in {source}") + log.warning(f"Security violation: Potential RCE payload detected in {source}, value: {value}") + raise HTTPException(status_code=422, detail="Invalid request parameters") if TRAVERSAL_PATTERN.search(value): - raise HTTPException(status_code=422, detail=f"Potential Path Traversal payload detected in {source}") + log.warning(f"Security violation: Potential Path Traversal payload detected in {source}, value: {value}") + raise HTTPException(status_code=422, detail="Invalid request parameters") if has_control_chars(value): - raise HTTPException(status_code=422, detail=f"Invalid control characters detected in {source}") + log.warning(f"Security violation: Invalid control characters detected in {source}") + raise HTTPException(status_code=422, detail="Invalid request parameters") def inspect_json(obj, path="body", check_whitelist=True): if isinstance(obj, dict): for key, value in obj.items(): if key in FORBIDDEN_JSON_KEYS: - raise HTTPException(status_code=422, detail=f"Forbidden JSON key detected: {path}.{key}") + log.warning(f"Security violation: Forbidden JSON key detected: {path}.{key}") + raise HTTPException(status_code=422, detail="Invalid request parameters") if check_whitelist and key not in ALLOWED_DATA_PARAMS: - raise HTTPException(status_code=422, detail=f"Unknown JSON key detected: {path}.{key}") + log.warning(f"Security violation: Unknown JSON key detected: {path}.{key}") + raise HTTPException(status_code=422, detail="Invalid request parameters") # Recurse. If the key is a dynamic container, we stop whitelist checking for children. should_check_subkeys = check_whitelist and (key not in DYNAMIC_KEYS) @@ -213,14 +220,16 @@ class RequestValidationMiddleware(BaseHTTPMiddleware): ALLOW_DUPLICATE_HEADERS = {'accept', 'accept-encoding', 'accept-language', 'accept-charset', 'cookie'} real_duplicates = [h for h in duplicate_headers if h not in ALLOW_DUPLICATE_HEADERS] if real_duplicates: - raise HTTPException(status_code=422, detail=f"Duplicate headers are not allowed: {real_duplicates}") + log.warning(f"Security violation: Duplicate headers detected: {real_duplicates}") + raise HTTPException(status_code=422, detail="Invalid request parameters") # Whitelist headers unknown_headers = [key for key in header_keys if key not in ALLOWED_HEADERS] if unknown_headers: filtered_unknown = [h for h in unknown_headers if not h.startswith('sec-')] if filtered_unknown: - raise HTTPException(status_code=422, detail=f"Unknown headers detected: {filtered_unknown}") + log.warning(f"Security violation: Unknown headers detected: {filtered_unknown}") + raise HTTPException(status_code=422, detail="Invalid request parameters") # Inspect header values for key, value in request.headers.items(): @@ -231,17 +240,20 @@ class RequestValidationMiddleware(BaseHTTPMiddleware): # 1. Query string limits # ------------------------- if len(request.url.query) > MAX_QUERY_LENGTH: - raise HTTPException(status_code=422, detail="Query string too long") + log.warning(f"Security violation: Query string too long") + raise HTTPException(status_code=422, detail="Invalid request parameters") params = request.query_params.multi_items() if len(params) > MAX_QUERY_PARAMS: - raise HTTPException(status_code=422, detail="Too many query parameters") + log.warning(f"Security violation: Too many query parameters") + raise HTTPException(status_code=422, detail="Invalid request parameters") # Check for unknown query parameters unknown_params = [key for key, _ in params if key not in ALLOWED_DATA_PARAMS] if unknown_params: - raise HTTPException(status_code=422, detail=f"Unknown query parameters detected: {unknown_params}") + log.warning(f"Security violation: Unknown query parameters detected: {unknown_params}") + raise HTTPException(status_code=422, detail="Invalid request parameters") # ------------------------- # 2. Duplicate parameters @@ -253,7 +265,8 @@ class RequestValidationMiddleware(BaseHTTPMiddleware): ] if duplicates: - raise HTTPException(status_code=422, detail=f"Duplicate query parameters are not allowed: {duplicates}") + log.warning(f"Security violation: Duplicate query parameters detected: {duplicates}") + raise HTTPException(status_code=422, detail="Invalid request parameters") # ------------------------- # 3. Query param inspection & Pagination @@ -267,11 +280,14 @@ class RequestValidationMiddleware(BaseHTTPMiddleware): try: size_val = int(value) if size_val > 50: - raise HTTPException(status_code=422, detail=f"Pagination size '{key}' cannot exceed 50") + log.warning(f"Security violation: Pagination size too large ({size_val})") + raise HTTPException(status_code=422, detail="Invalid request parameters") if size_val % 5 != 0: - raise HTTPException(status_code=422, detail=f"Pagination size '{key}' must be a multiple of 5") + log.warning(f"Security violation: Pagination size not multiple of 5 ({size_val})") + raise HTTPException(status_code=422, detail="Invalid request parameters") except ValueError: - raise HTTPException(status_code=422, detail=f"Pagination size '{key}' must be an integer") + log.warning(f"Security violation: Pagination size invalid value") + raise HTTPException(status_code=422, detail="Invalid request parameters") # ------------------------- # 4. Content-Type sanity @@ -281,7 +297,8 @@ class RequestValidationMiddleware(BaseHTTPMiddleware): content_type.startswith(t) for t in ("application/json", "multipart/form-data", "application/x-www-form-urlencoded") ): - raise HTTPException(status_code=422, detail="Unsupported Content-Type") + log.warning(f"Security violation: Unsupported Content-Type: {content_type}") + raise HTTPException(status_code=422, detail="Invalid request parameters") # ------------------------- # 5. Single source check (Query vs JSON Body) @@ -295,7 +312,8 @@ class RequestValidationMiddleware(BaseHTTPMiddleware): has_body = True if has_query and has_body: - raise HTTPException(status_code=422, detail="Parameters must be from a single source (query string or JSON body), mixed sources are not allowed") + log.warning(f"Security violation: Mixed parameters (query + JSON body)") + raise HTTPException(status_code=422, detail="Invalid request parameters") # ------------------------- # 6. JSON body inspection @@ -309,7 +327,8 @@ class RequestValidationMiddleware(BaseHTTPMiddleware): try: payload = json.loads(body) except json.JSONDecodeError: - raise HTTPException(status_code=422, detail="Invalid JSON body") + log.warning(f"Security violation: Invalid JSON body") + raise HTTPException(status_code=422, detail="Invalid request parameters") inspect_json(payload)