fix(projects): record IN-list bounds for project visibility filters

Both filters are bounded by one caller's team memberships and admin orgs. Also
cover a project whose team was deleted.
This commit is contained in:
ryan-crabbe-berri 2026-10-02 17:08:00 -07:00
parent ce2f7a60d1
commit 3a5028a0ed
3 changed files with 4 additions and 1 deletions

View file

@ -140,10 +140,12 @@ async def _can_view_team_projects(
def _visible_projects_where(team_ids: list[str], admin_org_ids: frozenset[str]) -> dict[str, object]:
# bounded-ok: one caller's team memberships
member_scope: Final[dict[str, object]] = {"team_id": {"in": team_ids}}
if not admin_org_ids:
return member_scope
org_scope: Final[dict[str, object]] = {
# bounded-ok: orgs one caller administers
"litellm_team_table": {"is": {"organization_id": {"in": sorted(admin_org_ids)}}}
}
return {"OR": [member_scope, org_scope]}

View file

@ -1,7 +1,6 @@
# Grandfathered findings of check_unbounded_in_lists.py: path::scope::kind::subject::occurrence.
# Fix a site and delete its line; regenerate with `check_unbounded_in_lists.py --update-baseline`.
enterprise/litellm_enterprise/proxy/common_utils/check_responses_cost.py CheckResponsesCost.check_responses_cost prisma id.in `[job.id for job in completed_jobs]` 0
enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py list_projects prisma team_id.in `user_team_ids` 0
litellm/integrations/shadow_eval_logger.py ShadowEvalLogger._active_jobs prisma job_id.in `[str(record.id) for record in records]` 0
litellm/llms/litellm_proxy/skills/handler.py LiteLLMSkillsHandler.list_skills prisma created_by.in `owner_scopes` 0
litellm/proxy/_experimental/mcp_server/db.py get_mcp_servers prisma server_id.in `server_ids` 0

View file

@ -1439,6 +1439,7 @@ _PROJECTS: Final = (
_ProjectRow("p-b1", "team-b1"),
_ProjectRow("p-orgless", "team-orgless"),
_ProjectRow("p-teamless", None),
_ProjectRow("p-deleted-team", "team-deleted"),
)
_USERS: Final = {
user.user_id: user
@ -1569,6 +1570,7 @@ async def test_project_info_allows_team_members_and_org_admins_of_the_team_org(p
("org-admin-a", "p-b1"),
("org-admin-a", "p-orgless"),
("org-admin-a", "p-teamless"),
("member", "p-deleted-team"),
],
)
async def test_project_info_denies_callers_who_cannot_view_the_team(project_access_db, user_id, project_id):