From 70684ca86fd995e23f039bdd50178827ef897b92 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 29 Jan 2026 11:19:24 +0530 Subject: [PATCH] Fix File access permissions for .retreive and .delete --- .../proxy/hooks/managed_files.py | 6 ++++- .../openai_files_endpoints/files_endpoints.py | 24 ++++++++++++++++--- .../proxy/hooks/test_managed_files.py | 4 ++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/enterprise/litellm_enterprise/proxy/hooks/managed_files.py b/enterprise/litellm_enterprise/proxy/hooks/managed_files.py index 62d4abee23..5ee3372cca 100644 --- a/enterprise/litellm_enterprise/proxy/hooks/managed_files.py +++ b/enterprise/litellm_enterprise/proxy/hooks/managed_files.py @@ -369,6 +369,8 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): if ( call_type == CallTypes.afile_content.value or call_type == CallTypes.afile_delete.value + or call_type == CallTypes.afile_retrieve.value + or call_type == CallTypes.afile_content.value ): await self.check_managed_file_id_access(data, user_api_key_dict) @@ -972,8 +974,10 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): delete_response = None specific_model_file_id_mapping = model_file_id_mapping.get(file_id) if specific_model_file_id_mapping: + # Remove conflicting keys from data to avoid duplicate keyword arguments + filtered_data = {k: v for k, v in data.items() if k not in ("model", "file_id")} for model_id, model_file_id in specific_model_file_id_mapping.items(): - delete_response = await llm_router.afile_delete(model=model_id, file_id=model_file_id, **data) # type: ignore + delete_response = await llm_router.afile_delete(model=model_id, file_id=model_file_id, **filtered_data) # type: ignore stored_file_object = await self.delete_unified_file_id( file_id, litellm_parent_otel_span diff --git a/litellm/proxy/openai_files_endpoints/files_endpoints.py b/litellm/proxy/openai_files_endpoints/files_endpoints.py index 2c6b378ae3..1c9909f2cc 100644 --- a/litellm/proxy/openai_files_endpoints/files_endpoints.py +++ b/litellm/proxy/openai_files_endpoints/files_endpoints.py @@ -812,7 +812,7 @@ async def get_file( version, ) - data: Dict = {} + data: Dict = {"file_id": file_id} try: custom_llm_provider = ( @@ -992,7 +992,7 @@ async def delete_file( version, ) - data: Dict = {} + data: Dict = {"file_id": file_id} try: custom_llm_provider = ( provider @@ -1001,6 +1001,22 @@ async def delete_file( or await get_custom_llm_provider_from_request_body(request=request) or "openai" ) + + # Call common_processing_pre_call_logic to trigger permission checks + base_llm_response_processor = ProxyBaseLLMRequestProcessing(data=data) + ( + data, + litellm_logging_obj, + ) = await base_llm_response_processor.common_processing_pre_call_logic( + request=request, + general_settings=general_settings, + user_api_key_dict=user_api_key_dict, + version=version, + proxy_logging_obj=proxy_logging_obj, + proxy_config=proxy_config, + route_type="afile_delete", + ) + # Include original request and headers in the data data = await add_litellm_data_to_request( data=data, @@ -1060,11 +1076,13 @@ async def delete_file( code=500, ) + # Remove file_id from data to avoid duplicate keyword argument + data_without_file_id = {k: v for k, v in data.items() if k != "file_id"} response = await managed_files_obj.afile_delete( file_id=file_id, litellm_parent_otel_span=user_api_key_dict.parent_otel_span, llm_router=llm_router, - **data, + **data_without_file_id, ) else: response = await litellm.afile_delete( diff --git a/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py b/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py index 4fa16066e4..6e951e1a5c 100644 --- a/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py +++ b/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py @@ -235,10 +235,10 @@ async def test_async_pre_call_hook_for_unified_finetuning_job(): @pytest.mark.asyncio -@pytest.mark.parametrize("call_type", ["afile_content", "afile_delete"]) +@pytest.mark.parametrize("call_type", ["afile_content", "afile_delete", "afile_retrieve"]) async def test_can_user_call_unified_file_id(call_type): """ - Test that on file retrieve, delete we check if the user has access to the file + Test that on file retrieve, delete, and content we check if the user has access to the file """ from litellm.proxy._types import UserAPIKeyAuth