Skip to content

Commit d37a5e8

Browse files
jmesnilclaude
andcommitted
fix: Reject SendMessage with mismatching contextId and taskId
When a message references an existing task but provides a contextId that doesn't match the task's context, reject it with an InvalidParamsError. This fixes #723 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3399bd3 commit d37a5e8

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,15 @@ private MessageSendSetup initMessageSend(MessageSendParams params, ServerCallCon
10261026

10271027
Task task = taskManager.getTask();
10281028
if (task != null) {
1029+
// Validate contextId matches the existing task's contextId
1030+
String messageContextId = params.message().contextId();
1031+
if (messageContextId != null && task.contextId() != null
1032+
&& !messageContextId.equals(task.contextId())) {
1033+
throw new InvalidParamsError(String.format(
1034+
"Message has a mismatched context ID (Task %s has contextId %s but message has contextId %s)",
1035+
task.id(), task.contextId(), messageContextId));
1036+
}
1037+
10291038
LOGGER.debug("Found task updating with message {}", params.message());
10301039
task = taskManager.updateWithMessage(params.message(), task);
10311040

server-common/src/test/java/io/a2a/server/requesthandlers/DefaultRequestHandlerTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.jupiter.api.Assertions.assertFalse;
55
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
66
import static org.junit.jupiter.api.Assertions.assertNotNull;
7+
import static org.junit.jupiter.api.Assertions.assertThrows;
78
import static org.junit.jupiter.api.Assertions.assertTrue;
89

910
import java.util.List;
@@ -30,6 +31,7 @@
3031
import io.a2a.spec.A2AError;
3132
import io.a2a.spec.Event;
3233
import io.a2a.spec.EventKind;
34+
import io.a2a.spec.InvalidParamsError;
3335
import io.a2a.spec.Message;
3436
import io.a2a.spec.MessageSendConfiguration;
3537
import io.a2a.spec.MessageSendParams;
@@ -509,4 +511,55 @@ void testAuthRequired_Resubscription() throws Exception {
509511
((TaskStatusUpdateEvent) completionEvent).status().state(),
510512
"Task should be completed");
511513
}
514+
515+
/**
516+
* Test: Reject SendMessage with mismatching contextId and taskId.
517+
* When a message references an existing task but provides a different contextId,
518+
* the request must be rejected with an InvalidParamsError.
519+
*/
520+
@Test
521+
void testRejectMismatchingContextId() throws Exception {
522+
// Arrange: Create an initial task to get valid identifiers
523+
CountDownLatch agentCompleted = new CountDownLatch(1);
524+
525+
agentExecutorExecute = (context, emitter) -> {
526+
emitter.complete();
527+
agentCompleted.countDown();
528+
};
529+
530+
Message initialMessage = Message.builder()
531+
.messageId("msg-1")
532+
.role(Message.Role.ROLE_USER)
533+
.contextId("original-context")
534+
.parts(new TextPart("initial message"))
535+
.build();
536+
537+
MessageSendParams initialParams = MessageSendParams.builder()
538+
.message(initialMessage)
539+
.configuration(DEFAULT_CONFIG)
540+
.build();
541+
542+
EventKind result = requestHandler.onMessageSend(initialParams, NULL_CONTEXT);
543+
assertInstanceOf(Task.class, result);
544+
Task task = (Task) result;
545+
assertTrue(agentCompleted.await(5, TimeUnit.SECONDS));
546+
547+
// Act & Assert: Send a follow-up message with matching taskId but wrong contextId
548+
Message mismatchedMessage = Message.builder()
549+
.messageId("msg-2")
550+
.role(Message.Role.ROLE_USER)
551+
.taskId(task.id())
552+
.contextId("wrong-context-does-not-exist")
553+
.parts(new TextPart("follow-up message"))
554+
.build();
555+
556+
MessageSendParams mismatchedParams = MessageSendParams.builder()
557+
.message(mismatchedMessage)
558+
.configuration(DEFAULT_CONFIG)
559+
.build();
560+
561+
InvalidParamsError error = assertThrows(InvalidParamsError.class,
562+
() -> requestHandler.onMessageSend(mismatchedParams, NULL_CONTEXT));
563+
assertTrue(error.getMessage().contains(task.id()));
564+
}
512565
}

0 commit comments

Comments
 (0)