From 799d79160afbd323a0b84baeeed7c2e076f02f74 Mon Sep 17 00:00:00 2001
From: user <70670632+stuxf@users.noreply.github.com>
Date: Fri, 1 May 2026 20:44:51 +0000
Subject: [PATCH] fix(proxy): match Prisma index names + extend listing to team
for user-keyed callers
Two follow-ups to the managed-resource isolation fix:
1. Rename the new composite indexes to match Prisma's auto-generated naming
convention (`
_created_by_team_id_created_at_idx`). The previous
`*_team_owner_created_at_idx` names left `prisma migrate diff` reporting
an outstanding `RENAME INDEX`, failing `test_aaaasschema_migration_check`.
2. Make `build_owner_filter` return an OR clause when the caller has both
a `user_id` and a `team_id`, so listings include team-shared resources
the same way `can_access_resource` already permits reading them. Without
this a user could fetch a team-shared resource by id but never see it
in their list view.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../migration.sql | 9 +++++---
.../base_llm/managed_resources/isolation.py | 22 +++++++++++++++----
.../test_managed_resource_isolation.py | 12 ++++++++--
3 files changed, 34 insertions(+), 9 deletions(-)
diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260501195714_managed_resource_team_owner/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260501195714_managed_resource_team_owner/migration.sql
index 9020f41fe8..793a02c08a 100644
--- a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260501195714_managed_resource_team_owner/migration.sql
+++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260501195714_managed_resource_team_owner/migration.sql
@@ -13,6 +13,9 @@ ALTER TABLE "LiteLLM_ManagedFileTable" ADD COLUMN IF NOT EXISTS "created_by_team
ALTER TABLE "LiteLLM_ManagedObjectTable" ADD COLUMN IF NOT EXISTS "created_by_team_id" TEXT;
ALTER TABLE "LiteLLM_ManagedVectorStoreTable" ADD COLUMN IF NOT EXISTS "created_by_team_id" TEXT;
-CREATE INDEX IF NOT EXISTS "LiteLLM_ManagedFileTable_team_owner_created_at_idx" ON "LiteLLM_ManagedFileTable" ("created_by_team_id", "created_at" DESC);
-CREATE INDEX IF NOT EXISTS "LiteLLM_ManagedObjectTable_team_owner_created_at_idx" ON "LiteLLM_ManagedObjectTable" ("created_by_team_id", "created_at" DESC);
-CREATE INDEX IF NOT EXISTS "LiteLLM_ManagedVectorStoreTable_team_owner_created_at_idx" ON "LiteLLM_ManagedVectorStoreTable" ("created_by_team_id", "created_at" DESC);
+-- Index names follow Prisma's auto-generated convention so `prisma migrate diff`
+-- against the schema is clean. Postgres caps identifier length at 63 chars,
+-- which truncates the vector-store name to `_created__idx`.
+CREATE INDEX IF NOT EXISTS "LiteLLM_ManagedFileTable_created_by_team_id_created_at_idx" ON "LiteLLM_ManagedFileTable" ("created_by_team_id", "created_at" DESC);
+CREATE INDEX IF NOT EXISTS "LiteLLM_ManagedObjectTable_created_by_team_id_created_at_idx" ON "LiteLLM_ManagedObjectTable" ("created_by_team_id", "created_at" DESC);
+CREATE INDEX IF NOT EXISTS "LiteLLM_ManagedVectorStoreTable_created_by_team_id_created__idx" ON "LiteLLM_ManagedVectorStoreTable" ("created_by_team_id", "created_at" DESC);
diff --git a/litellm/llms/base_llm/managed_resources/isolation.py b/litellm/llms/base_llm/managed_resources/isolation.py
index dea7f1e23d..267643e1f5 100644
--- a/litellm/llms/base_llm/managed_resources/isolation.py
+++ b/litellm/llms/base_llm/managed_resources/isolation.py
@@ -38,17 +38,31 @@ def build_owner_filter(
- ``{"created_by": }`` for user-keyed callers.
- ``{"created_by_team_id": }`` for service-account callers
that have a team but no user_id.
+ - ``{"OR": [...]}`` when the caller has both — listing must include
+ both their own resources and team-shared ones so it stays consistent
+ with ``can_access_resource``.
- ``None`` means deny: callers MUST skip the query rather than fall
back to an unscoped fetch.
"""
if _user_has_admin_view(user_api_key_dict):
return {}
- if user_api_key_dict.user_id is not None:
- return {"created_by": user_api_key_dict.user_id}
+ user_id = user_api_key_dict.user_id
+ team_id = user_api_key_dict.team_id
- if user_api_key_dict.team_id is not None:
- return {"created_by_team_id": user_api_key_dict.team_id}
+ if user_id is not None and team_id is not None:
+ return {
+ "OR": [
+ {"created_by": user_id},
+ {"created_by_team_id": team_id},
+ ]
+ }
+
+ if user_id is not None:
+ return {"created_by": user_id}
+
+ if team_id is not None:
+ return {"created_by_team_id": team_id}
return None
diff --git a/tests/test_litellm/llms/base_llm/test_managed_resource_isolation.py b/tests/test_litellm/llms/base_llm/test_managed_resource_isolation.py
index b11fa351e7..a6bc9874ac 100644
--- a/tests/test_litellm/llms/base_llm/test_managed_resource_isolation.py
+++ b/tests/test_litellm/llms/base_llm/test_managed_resource_isolation.py
@@ -34,9 +34,17 @@ def test_owner_filter_service_account_scoped_to_team():
assert build_owner_filter(service_account) == {"created_by_team_id": "team-eng"}
-def test_owner_filter_user_id_takes_precedence_over_team_id():
+def test_owner_filter_user_with_team_returns_or_filter():
+ """List view must mirror `can_access_resource`: a user-keyed caller in a
+ team can also access team-shared resources, so the listing returns both
+ their own records and team records via an OR filter."""
user = UserAPIKeyAuth(user_id="alice", team_id="team-eng")
- assert build_owner_filter(user) == {"created_by": "alice"}
+ assert build_owner_filter(user) == {
+ "OR": [
+ {"created_by": "alice"},
+ {"created_by_team_id": "team-eng"},
+ ]
+ }
def test_owner_filter_no_identity_returns_none():