From c8e1fc003d77d934b08bc3bf6ec7c32199162596 Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Sat, 26 Sep 2026 12:57:37 -0700 Subject: [PATCH] fix(agents): isolate human subject reads and reject inactive authentication writes --- .../proxy/agent_endpoints/identity_store.py | 8 ++++--- .../agent_endpoints/test_identity_store.py | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/agent_endpoints/identity_store.py b/litellm/proxy/agent_endpoints/identity_store.py index f8072f174cd..87512c779fe 100644 --- a/litellm/proxy/agent_endpoints/identity_store.py +++ b/litellm/proxy/agent_endpoints/identity_store.py @@ -98,9 +98,6 @@ class AgentIdentityStore: row: Final = await self.identities.table.find_unique(where=where) except Exception: return AgentIdentityFailure(code="policy_unavailable", message="Agent identity could not be loaded") - proven: Final = await self.subject(issuer, tenant, claims.get("oid")) - if isinstance(proven, AgentIdentityFailure): - return proven if row is None: return await self.unbound_client(where) agent: Final = await self.agent(row.agent_id) @@ -124,6 +121,9 @@ class AgentIdentityStore: mode=subject.mode, subject_oid=subject.oid, ) + proven: Final = await self.subject(issuer, tenant, claims.get("oid")) + if isinstance(proven, AgentIdentityFailure): + return proven human: Final = ( VerifiedHumanSubject.model_validate(proven.model_dump()) if proven is not None @@ -170,6 +170,8 @@ class AgentIdentityStore: where: Final[LiteLLM_AgentIdentityWhereInput] = { "agent_id": context.agent_id, "revision": context.binding_revision, + "active": True, + "agent": {"is": {"enabled": True, "identity_managed": True}}, } data: Final[LiteLLM_AgentIdentityUpdateManyMutationInput] = { "last_authenticated_at": datetime.now(timezone.utc) diff --git a/tests/test_litellm/proxy/agent_endpoints/test_identity_store.py b/tests/test_litellm/proxy/agent_endpoints/test_identity_store.py index ae1f7f7cca9..faace1ccf20 100644 --- a/tests/test_litellm/proxy/agent_endpoints/test_identity_store.py +++ b/tests/test_litellm/proxy/agent_endpoints/test_identity_store.py @@ -158,7 +158,12 @@ async def test_rebinding_during_authentication_does_not_mark_new_identity_verifi result: Final = await store.record_authentication(context) assert isinstance(result, AgentIdentityFailure) assert "changed" in result.message - assert identities.update_many.call_args.kwargs["where"] == {"agent_id": "agent-one", "revision": "old-revision"} + assert identities.update_many.call_args.kwargs["where"] == { + "agent_id": "agent-one", + "revision": "old-revision", + "active": True, + "agent": {"is": {"enabled": True, "identity_managed": True}}, + } @pytest.mark.asyncio @@ -379,3 +384,19 @@ async def test_resolver_preserves_unconfigured_and_unrelated_authentication() -> store, _, identities, _ = setup_store() identities.find_unique.return_value = None assert await store.resolve_verified_claims(CLAIMS) is None + + +@pytest.mark.asyncio +@pytest.mark.parametrize("registered", [True, False]) +async def test_application_and_unregistered_clients_do_not_depend_on_human_subject_storage(registered: bool) -> None: + store, _, identities, humans = setup_store() + identities.find_unique.return_value = BINDING if registered else None + humans.find_unique.side_effect = RuntimeError("subject database unavailable") + result: Final = await store.resolve_verified_claims(CLAIMS) + if registered: + assert isinstance(result, ManagedAgentContext) + assert result.mode == "autonomous" + assert result.user_id is None + else: + assert result is None + humans.find_unique.assert_not_awaited()