diff --git a/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java b/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java index 92c10cd97..1bf00d998 100644 --- a/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java +++ b/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java @@ -165,7 +165,26 @@ private ListSessionsResponse parseListSessionsResponse( @Override public Single listEvents(String appName, String userId, String sessionId) { validateSessionId(sessionId); - return listEventsInternal(appName, sessionId, /* filter= */ null); + String reasoningEngineId = parseReasoningEngineId(appName); + return client + .getSession(reasoningEngineId, sessionId) + .flatMapSingle( + getSessionResponseMap -> { + // Enforce ownership using the owner reported by the backend, not the + // requested user id, mirroring the check getSession/deleteSession use. + // Deny as empty so existence is not revealed to a non-owner. + String ownerUserId = + Optional.ofNullable(getSessionResponseMap.get("userId")) + .map(JsonNode::asText) + .orElse(null); + if (!userId.equals(ownerUserId)) { + return Single.just(ListEventsResponse.builder().build()); + } + return listEventsInternal(appName, sessionId, /* filter= */ null); + }) + // No session found at all (rather than an ownership mismatch): behave the + // same as before, returning an empty response instead of erroring. + .defaultIfEmpty(ListEventsResponse.builder().build()); } private Single listEventsInternal( diff --git a/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java index db3556956..26569e3ec 100644 --- a/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java @@ -422,6 +422,17 @@ public void getSession_wrongUser_returnsEmpty() { .isNull(); } + @Test + public void listEvents_wrongUser_returnsEmpty() { + // Session "1" belongs to "user" and has an event in eventMap (MOCK_EVENT_STRING). + // A different user must not be able to read those events - mirrors + // getSession_wrongUser_returnsEmpty above, applied to listEvents. + ListEventsResponse response = + vertexAiSessionService.listEvents("123", "attacker", "1").blockingGet(); + + assertThat(response.events()).isEmpty(); + } + @Test public void deleteSession_wrongUser_deniedAndSessionKept() { // The ownership error surfaces on subscription, so hoist the Completable out.