From 2d2782e4a6593ed98194fe0802be8a8327cb0a4f Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:00:02 -0700 Subject: [PATCH] fix(agents): cap a managed agent at the invoking team's agents resolve_agent_access returned the managed policy's grants before the agent_caller ceiling was applied, so a managed agent acting on behalf of a user reached agents that user's team was never granted. Intersect with the caller ceiling the unmanaged path already honours. --- .../auth/agent_permission_handler.py | 4 +++- .../auth/test_agent_permission_handler.py | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/agent_endpoints/auth/agent_permission_handler.py b/litellm/proxy/agent_endpoints/auth/agent_permission_handler.py index 49171c313f4..96f130abeef 100644 --- a/litellm/proxy/agent_endpoints/auth/agent_permission_handler.py +++ b/litellm/proxy/agent_endpoints/auth/agent_permission_handler.py @@ -650,7 +650,9 @@ async def _managed_actor_agent_access(auth: UserAPIKeyAuth) -> AgentAccess: from litellm.proxy.agent_endpoints.auth.agent_access_groups import resolve_managed_agent_ceilings ceilings: Final = await resolve_managed_agent_ceilings(agent) - capped: Final = frozenset(target for target in own if all(target in ceiling.agent_ids for ceiling in ceilings)) + grouped: Final = frozenset(target for target in own if all(target in ceiling.agent_ids for ceiling in ceilings)) + caller: Final = await AgentRequestHandler._agent_caller_access(auth) + capped: Final = grouped if isinstance(caller, UnrestrictedAgentAccess) else grouped & caller.agent_ids context: Final = auth.managed_agent_context if context is None or context.mode == "autonomous": return RestrictedAgentAccess(capped) diff --git a/tests/test_litellm/proxy/agent_endpoints/auth/test_agent_permission_handler.py b/tests/test_litellm/proxy/agent_endpoints/auth/test_agent_permission_handler.py index d516aba862d..2b8aaea8981 100644 --- a/tests/test_litellm/proxy/agent_endpoints/auth/test_agent_permission_handler.py +++ b/tests/test_litellm/proxy/agent_endpoints/auth/test_agent_permission_handler.py @@ -237,6 +237,29 @@ class TestAgentRequestHandler: frozenset() ) + async def test_managed_agent_acting_for_a_user_is_capped_at_the_invoking_teams_agents(self): + """The managed path must honour the invoking team's ceiling the same way the unmanaged path does: + the agent's own policy grants alpha and beta, but the human who invoked it reaches only beta.""" + from litellm.types.agents import AgentResponse + + managed: Final = UserAPIKeyAuth(api_key="test-key", user_id="test-user", agent_id="actor") + managed.managed_agent_policy = AgentResponse( + agent_id="actor", + agent_name="Actor", + agent_card_params={}, + object_permission={"object_permission_id": "own", "agents": ["agent-alpha", "agent-beta"]}, + ) + managed.agent_caller = AgentCaller(user_id="alice", team_id="callers") + + with patch.object( # test-quality-ok: the team resolver reads proxy_server globals with no injection seam + AgentRequestHandler, + "_get_allowed_agents_for_team", + self._team_grants({"callers": RestrictedAgentAccess(frozenset({"agent-beta"}))}), + ): + assert await AgentRequestHandler.resolve_agent_access(managed) == RestrictedAgentAccess( + frozenset({"agent-beta"}) + ) + async def test_agent_key_acting_for_an_ungranted_caller_keeps_its_own_agents(self): agent_key: Final = self._key_granting(["agent-alpha"], agent_id="caller-agent") agent_key.agent_caller = AgentCaller(user_id="alice", team_id="callers")