fix(proxy/batches): fail closed when the managed file ownership lookup errors

A lookup exception previously fell back to dispatching the original
unified id with the ownership gate unexecuted; the managed-files
deployment hook maps unified ids from cache without re-checking
ownership, so a database outage let a caller dispatch another tenant's
file. Raise a clear 503 instead and lock the behavior with a regression
test. No-database and no-row cases still fall back unchanged
This commit is contained in:
Yucheng Zhu 2026-07-23 23:59:36 -07:00
parent f8fc629286
commit 0a33ea40c6
2 changed files with 22 additions and 12 deletions

View file

@ -51,11 +51,13 @@ async def _resolve_managed_input_file_storage_url(
) -> "str | None":
"""Resolve a managed (unified) input_file_id to its backend storage_url.
Returns None when the proxy has no database, the lookup fails, no managed
file row exists, or the row has no storage_url; callers fall back to
dispatching the original id so the managed-files deployment hook can still
map it via model_file_id_mapping. Raises a 404 when the caller does not
own the managed file.
Returns None when the proxy has no database, no managed file row exists,
or the row has no storage_url; callers fall back to dispatching the
original id so the managed-files deployment hook can still map it via
model_file_id_mapping. Raises a 404 when the caller does not own the
managed file and a 503 when the lookup fails, so an unverifiable id is
never dispatched (the deployment hook maps ids from cache without
re-checking ownership).
"""
from litellm.proxy.proxy_server import prisma_client
@ -65,7 +67,10 @@ async def _resolve_managed_input_file_storage_url(
db_file = await ManagedFileRepository(prisma_client).table.find_first(where={"unified_file_id": input_file_id})
except Exception as e:
verbose_proxy_logger.warning("create_batch: managed file lookup failed for %s: %s", input_file_id, e)
return None
raise HTTPException(
status_code=503,
detail={"error": "Unable to verify managed file access; please retry"},
)
if db_file is None:
return None
if not can_access_resource(

View file

@ -605,10 +605,12 @@ async def test_create__unified_file_id_other_tenant_gets_404(harness):
@pytest.mark.asyncio
async def test_create__unified_file_id_db_error_falls_back_to_raw_id(harness):
"""A database failure during resolution must not abort batch creation:
the original id is dispatched so the managed-files deployment hook can
still map it via model_file_id_mapping."""
async def test_create__unified_file_id_db_error_fails_closed_503(harness):
"""A lookup failure must fail closed: the ownership gate could not run, and
the managed-files deployment hook maps unified ids from cache without
re-checking ownership, so dispatching the unverified id would let a caller
use another tenant's file during a database outage. Expect a clear 503 and
no dispatch."""
set_body(
harness,
{
@ -629,9 +631,12 @@ async def test_create__unified_file_id_db_error_falls_back_to_raw_id(harness):
patch.object(proxy_server, "prisma_client", MagicMock()),
patch.object(endpoints, "ManagedFileRepository", fake_repo_cls),
):
await call_create(harness)
with pytest.raises(ProxyException) as exc:
await call_create(harness)
assert harness.router_kwargs()["input_file_id"] == "litellm_proxy_unified_id"
assert exc.value.code == "503"
harness.router_acreate.assert_not_called()
harness.litellm_acreate.assert_not_called()
@pytest.mark.asyncio