From 23c27efd6561da724e16bb41a081360fff39aecd Mon Sep 17 00:00:00 2001 From: curo1305 Date: Thu, 4 Jun 2026 23:09:20 +0200 Subject: [PATCH] fix(06): CR-03 replace raw X-Forwarded-For reads with get_client_ip() Replace all instances of request.headers.get("X-Forwarded-For") in cloud.py (connect_webdav, delete_connection) and documents.py (upload, confirm, delete) with get_client_ip(request) from deps.utils. Add get_client_ip import to both files. Prevents XFF spoofing in audit logs. Co-Authored-By: Claude Sonnet 4.6 --- backend/api/cloud.py | 5 +++-- backend/api/documents.py | 15 ++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/backend/api/cloud.py b/backend/api/cloud.py index 69b970a..dd9f32c 100644 --- a/backend/api/cloud.py +++ b/backend/api/cloud.py @@ -37,6 +37,7 @@ from config import settings from db.models import CloudConnection, User from deps.auth import get_regular_user from deps.db import get_db +from deps.utils import get_client_ip from services.audit import write_audit_log from services.rate_limiting import account_limiter from storage.cloud_utils import encrypt_credentials, decrypt_credentials, validate_cloud_url @@ -626,7 +627,7 @@ async def connect_webdav( conn = await _upsert_cloud_connection(session, current_user.id, body.provider, credentials_enc) await session.flush() - _ip = request.headers.get("X-Forwarded-For") or (request.client.host if request.client else None) + _ip = get_client_ip(request) await write_audit_log( session, event_type="cloud.connected", @@ -763,7 +764,7 @@ async def delete_connection( from services.cloud_cache import invalidate_provider_cache # lazy import invalidate_provider_cache(str(current_user.id), provider) - _ip = request.headers.get("X-Forwarded-For") or (request.client.host if request.client else None) + _ip = get_client_ip(request) await write_audit_log( session, diff --git a/backend/api/documents.py b/backend/api/documents.py index 18628b8..767d0b1 100644 --- a/backend/api/documents.py +++ b/backend/api/documents.py @@ -36,6 +36,7 @@ from config import settings from db.models import CloudConnection, Document, Folder, Quota, Share, User from deps.auth import get_regular_user from deps.db import get_db +from deps.utils import get_client_ip from services import classifier, storage from services.audit import write_audit_log from services.rate_limiting import account_limiter @@ -272,9 +273,7 @@ async def upload_document( ) session.add(doc) - _ip = ( - request.headers.get("X-Forwarded-For") or (request.client.host if request.client else None) - ) if request else None + _ip = get_client_ip(request) if request else None await write_audit_log( session, event_type="document.uploaded", @@ -378,10 +377,7 @@ async def confirm_upload( doc.status = "uploaded" # D-13: document uploaded event — size_bytes + storage_backend only, NO filename, NO extracted_text (T-04-07-02) - # TRUST BOUNDARY: X-Forwarded-For is client-controlled — for audit logging only, - # not for auth/access control. Use a trusted reverse proxy in production to - # overwrite this header with the real remote IP before it reaches FastAPI. - _ip = request.headers.get("X-Forwarded-For") or (request.client.host if request.client else None) + _ip = get_client_ip(request) await write_audit_log( session, event_type="document.uploaded", @@ -664,10 +660,7 @@ async def delete_document( is_cloud = doc.storage_backend != "minio" _doc_size = doc.size_bytes _doc_id = doc.id - # TRUST BOUNDARY: X-Forwarded-For is client-controlled — for audit logging only, - # not for auth/access control. Use a trusted reverse proxy in production to - # overwrite this header with the real remote IP before it reaches FastAPI. - _ip = request.headers.get("X-Forwarded-For") or (request.client.host if request.client else None) + _ip = get_client_ip(request) # Cloud routing: attempt provider delete unless remove_only is set if is_cloud and not remove_only: