Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public TaskPushNotificationConfig createTaskPushNotificationConfiguration(TaskPu
@Nullable ClientCallContext context) throws A2AClientException {
checkNotNullParam("request", request);

String configId = request.pushNotificationConfig().id();
String configId = request.config().id();
io.a2a.grpc.CreateTaskPushNotificationConfigRequest grpcRequest = io.a2a.grpc.CreateTaskPushNotificationConfigRequest.newBuilder()
.setTaskId(request.taskId())
.setConfig(ToProto.taskPushNotificationConfig(request).getPushNotificationConfig())
Expand All @@ -254,15 +254,15 @@ public TaskPushNotificationConfig createTaskPushNotificationConfiguration(TaskPu
public TaskPushNotificationConfig getTaskPushNotificationConfiguration(GetTaskPushNotificationConfigParams request,
@Nullable ClientCallContext context) throws A2AClientException {
checkNotNullParam("request", request);
checkNotNullParam("taskId", request.id());
if(request.pushNotificationConfigId() == null) {
checkNotNullParam("taskId", request.taskId());
if(request.id() == null) {
throw new IllegalArgumentException("Id must not be null");
}

io.a2a.grpc.GetTaskPushNotificationConfigRequest grpcRequest = io.a2a.grpc.GetTaskPushNotificationConfigRequest.newBuilder()
.setTaskId(request.id())
.setTaskId(request.taskId())
.setTenant(resolveTenant(request.tenant()))
.setId(request.pushNotificationConfigId())
.setId(request.id())
.build();
PayloadAndHeaders payloadAndHeaders = applyInterceptors(GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD, grpcRequest, agentCard, context);

Expand Down Expand Up @@ -304,8 +304,8 @@ public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationC
checkNotNullParam("request", request);

io.a2a.grpc.DeleteTaskPushNotificationConfigRequest grpcRequest = io.a2a.grpc.DeleteTaskPushNotificationConfigRequest.newBuilder()
.setTaskId(request.id())
.setId(request.pushNotificationConfigId())
.setTaskId(request.taskId())
.setId(request.id())
.setTenant(resolveTenant(request.tenant()))
.build();
PayloadAndHeaders payloadAndHeaders = applyInterceptors(DELETE_TASK_PUSH_NOTIFICATION_CONFIG_METHOD, grpcRequest, agentCard, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ public void testA2AClientGetTaskPushNotificationConfig() throws Exception {
JSONRPCTransport client = new JSONRPCTransport("http://localhost:4001");
TaskPushNotificationConfig taskPushNotificationConfig = client.getTaskPushNotificationConfiguration(
new GetTaskPushNotificationConfigParams("de38c76d-d54c-436c-8b9f-4c2703648d64", "c295ea44-7543-4f78-b524-7a38915ad6e4"), null);
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.config();
assertNotNull(pushNotificationConfig);
assertEquals("https://example.com/callback", pushNotificationConfig.url());
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
Expand Down Expand Up @@ -339,7 +339,7 @@ public void testA2AClientCreateTaskPushNotificationConfig() throws Exception {
.url("https://example.com/callback")
.authentication(new AuthenticationInfo("jwt", null))
.build(), ""), null);
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.config();
assertNotNull(pushNotificationConfig);
assertEquals("https://example.com/callback", pushNotificationConfig.url());
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ public TaskPushNotificationConfig createTaskPushNotificationConfiguration(TaskPu
= io.a2a.grpc.CreateTaskPushNotificationConfigRequest.newBuilder();
builder.setConfig(ProtoUtils.ToProto.taskPushNotificationConfig(request).getPushNotificationConfig())
.setTaskId(request.taskId());
if (request.pushNotificationConfig().id() != null) {
builder.setConfigId(request.pushNotificationConfig().id());
if (request.config().id() != null) {
builder.setConfigId(request.config().id());
}
PayloadAndHeaders payloadAndHeaders = applyInterceptors(SET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD, builder, agentCard, context);
try {
Expand All @@ -296,14 +296,14 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(GetTaskPu
io.a2a.grpc.GetTaskPushNotificationConfigRequest.Builder builder
= io.a2a.grpc.GetTaskPushNotificationConfigRequest.newBuilder();
StringBuilder url = new StringBuilder(Utils.buildBaseUrl(agentInterface, request.tenant()));
String configId = request.pushNotificationConfigId();
String configId = request.id();
if (configId != null && !configId.isEmpty()) {
builder.setId(configId).setTaskId(request.id());
url.append(String.format("/tasks/%1s/pushNotificationConfigs/%2s", request.id(), configId));
builder.setId(configId).setTaskId(request.taskId());
url.append(String.format("/tasks/%1s/pushNotificationConfigs/%2s", request.taskId(), configId));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The taskId and id (configId) parameters are used to construct a URL path without proper URL encoding. If these identifiers contain special characters such as /, ?, or #, it could lead to path traversal or malformed requests to the agent. It is recommended to URL-encode these parameters before including them in the URL string.

Suggested change
url.append(String.format("/tasks/%1s/pushNotificationConfigs/%2s", request.taskId(), configId));
url.append(String.format("/tasks/%1s/pushNotificationConfigs/%2s", URLEncoder.encode(request.taskId(), StandardCharsets.UTF_8), URLEncoder.encode(configId, StandardCharsets.UTF_8)));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong as those aren't query parameters

} else {
// Use trailing slash to distinguish GET from LIST
builder.setTaskId(request.id());
url.append(String.format("/tasks/%1s/pushNotificationConfigs/", request.id()));
builder.setTaskId(request.taskId());
url.append(String.format("/tasks/%1s/pushNotificationConfigs/", request.taskId()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The taskId parameter is used to construct a URL path without proper URL encoding. This could lead to path traversal if the identifier contains characters like /. It is recommended to URL-encode the taskId before including it in the URL string.

Suggested change
url.append(String.format("/tasks/%1s/pushNotificationConfigs/", request.taskId()));
url.append(String.format("/tasks/%1s/pushNotificationConfigs/", URLEncoder.encode(request.taskId(), StandardCharsets.UTF_8)));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong as those aren't query parameters

}
PayloadAndHeaders payloadAndHeaders = applyInterceptors(GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD, builder,
agentCard, context);
Expand Down Expand Up @@ -367,7 +367,7 @@ public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationC
PayloadAndHeaders payloadAndHeaders = applyInterceptors(DELETE_TASK_PUSH_NOTIFICATION_CONFIG_METHOD, builder,
agentCard, context);
try {
String url = Utils.buildBaseUrl(agentInterface, request.tenant()) + String.format("/tasks/%1s/pushNotificationConfigs/%2s", request.id(), request.pushNotificationConfigId());
String url = Utils.buildBaseUrl(agentInterface, request.tenant()) + String.format("/tasks/%1s/pushNotificationConfigs/%2s", request.taskId(), request.id());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The taskId and id parameters are used to construct a URL path without proper URL encoding. This can lead to path traversal or malformed requests if these identifiers contain special characters. It is recommended to URL-encode these parameters before including them in the URL string.

Suggested change
String url = Utils.buildBaseUrl(agentInterface, request.tenant()) + String.format("/tasks/%1s/pushNotificationConfigs/%2s", request.taskId(), request.id());
String url = Utils.buildBaseUrl(agentInterface, request.tenant()) + String.format("/tasks/%1s/pushNotificationConfigs/%2s", URLEncoder.encode(request.taskId(), StandardCharsets.UTF_8), URLEncoder.encode(request.id(), StandardCharsets.UTF_8));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong as those aren't query parameters

A2AHttpClient.DeleteBuilder deleteBuilder = httpClient.createDelete().url(url);
if (payloadAndHeaders.getHeaders() != null) {
for (Map.Entry<String, String> entry : payloadAndHeaders.getHeaders().entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ public void testCreateTaskPushNotificationConfiguration() throws Exception {
new AuthenticationInfo("jwt", null))
.build(), "tenant");
TaskPushNotificationConfig taskPushNotificationConfig = client.createTaskPushNotificationConfiguration(pushedConfig, null);
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.config();
assertNotNull(pushNotificationConfig);
assertEquals("https://example.com/callback", pushNotificationConfig.url());
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
Expand All @@ -336,7 +336,7 @@ public void testGetTaskPushNotificationConfiguration() throws Exception {
RestTransport client = new RestTransport(CARD);
TaskPushNotificationConfig taskPushNotificationConfig = client.getTaskPushNotificationConfiguration(
new GetTaskPushNotificationConfigParams("de38c76d-d54c-436c-8b9f-4c2703648d64", "10"), null);
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.config();
assertNotNull(pushNotificationConfig);
assertEquals("https://example.com/callback", pushNotificationConfig.url());
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
Expand All @@ -363,14 +363,14 @@ public void testListTaskPushNotificationConfigurations() throws Exception {
ListTaskPushNotificationConfigResult result = client.listTaskPushNotificationConfigurations(
new ListTaskPushNotificationConfigParams("de38c76d-d54c-436c-8b9f-4c2703648d64"), null);
assertEquals(2, result.configs().size());
PushNotificationConfig pushNotificationConfig = result.configs().get(0).pushNotificationConfig();
PushNotificationConfig pushNotificationConfig = result.configs().get(0).config();
assertNotNull(pushNotificationConfig);
assertEquals("https://example.com/callback", pushNotificationConfig.url());
assertEquals("10", pushNotificationConfig.id());
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
assertEquals("jwt", authenticationInfo.scheme());
assertEquals("", authenticationInfo.credentials());
pushNotificationConfig = result.configs().get(1).pushNotificationConfig();
pushNotificationConfig = result.configs().get(1).config();
assertNotNull(pushNotificationConfig);
assertEquals("https://test.com/callback", pushNotificationConfig.url());
assertEquals("5", pushNotificationConfig.id());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ public TaskPushNotificationConfig createTaskPushNotificationConfiguration(TaskPu
if (request.taskId() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, request.taskId());
}
if (request.pushNotificationConfig() != null && request.pushNotificationConfig().id() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, request.pushNotificationConfig().id());
if (request.config() != null && request.config().id() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, request.config().id());
}
if (extractRequest()) {
spanBuilder.setAttribute(GENAI_REQUEST, request.toString());
Expand Down Expand Up @@ -276,11 +276,11 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(GetTaskPu
ClientCallContext clientContext = createContext(context);
SpanBuilder spanBuilder = tracer.spanBuilder(A2AMethods.GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD).setSpanKind(SpanKind.CLIENT);
spanBuilder.setAttribute(GENAI_OPERATION_NAME, A2AMethods.GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD);
if (request.id() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, request.id());
if (request.taskId() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, request.taskId());
}
if (request.pushNotificationConfigId() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, request.pushNotificationConfigId());
if (request.id() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, request.id());
}
if (extractRequest()) {
spanBuilder.setAttribute(GENAI_REQUEST, request.toString());
Expand Down Expand Up @@ -345,11 +345,11 @@ public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationC
if (extractRequest()) {
spanBuilder.setAttribute(GENAI_REQUEST, request.toString());
}
if (request.id() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, request.id());
if (request.taskId() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, request.taskId());
}
if (request.pushNotificationConfigId() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, request.pushNotificationConfigId());
if (request.id() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, request.id());
}
Span span = spanBuilder.startSpan();
try (Scope scope = span.makeCurrent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ public TaskPushNotificationConfig onCreateTaskPushNotificationConfig(TaskPushNot
if (params.taskId() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, params.taskId());
}
if (params.pushNotificationConfig() != null && params.pushNotificationConfig().id() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, params.pushNotificationConfig().id());
if (params.config() != null && params.config().id() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, params.config().id());
}
if (extractRequest()) {
spanBuilder.setAttribute(GENAI_REQUEST, params.toString());
Expand Down Expand Up @@ -331,11 +331,11 @@ public TaskPushNotificationConfig onGetTaskPushNotificationConfig(GetTaskPushNot
.setSpanKind(SpanKind.SERVER)
.setAttribute(GENAI_OPERATION_NAME, A2AMethods.GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD);

if (params.id() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, params.id());
if (params.taskId() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, params.taskId());
}
if (params.pushNotificationConfigId() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, params.pushNotificationConfigId());
if (params.id() != null) {
spanBuilder.setAttribute(GENAI_CONFIG_ID, params.id());
}
if (extractRequest()) {
spanBuilder.setAttribute(GENAI_REQUEST, params.toString());
Expand Down Expand Up @@ -436,8 +436,8 @@ public void onDeleteTaskPushNotificationConfig(DeleteTaskPushNotificationConfigP
if (extractRequest()) {
spanBuilder.setAttribute(GENAI_REQUEST, params.toString());
}
if (params.id() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, params.id());
if (params.taskId() != null) {
spanBuilder.setAttribute(GENAI_TASK_ID, params.taskId());
}

Span span = spanBuilder.startSpan();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ public void testJpaDatabasePushNotificationConfigStoreIntegration() throws Excep

assertNotNull(storedConfig);
assertEquals(taskId, storedConfig.taskId());
assertEquals("test-config-1", storedConfig.pushNotificationConfig().id());
assertEquals("http://localhost:9999/mock-endpoint", storedConfig.pushNotificationConfig().url());
assertEquals("test-token-123", storedConfig.pushNotificationConfig().token());
assertEquals("test-config-1", storedConfig.config().id());
assertEquals("http://localhost:9999/mock-endpoint", storedConfig.config().url());
assertEquals("test-token-123", storedConfig.config().token());

// Step 4: Update the task to trigger the notification
Message updateMessage = Message.builder()
Expand Down Expand Up @@ -239,10 +239,10 @@ public void testPaginationWithPageToken() {

// Verify NO overlap between pages - collect all IDs from both pages
List<String> firstPageIds = firstPage.configs().stream()
.map(c -> c.pushNotificationConfig().id())
.map(c -> c.config().id())
.toList();
List<String> secondPageIds = secondPage.configs().stream()
.map(c -> c.pushNotificationConfig().id())
.map(c -> c.config().id())
.toList();

// Check that no ID from first page appears in second page
Expand Down Expand Up @@ -471,10 +471,10 @@ public void testMultipleTasksDoNotInterfere() {
assertEquals(2, result2.configs().size(), "Task2 should have 2 configs");

List<String> task1Ids = result1.configs().stream()
.map(c -> taskId1 + c.pushNotificationConfig().id())
.map(c -> taskId1 + c.config().id())
.toList();
List<String> task2Ids = result2.configs().stream()
.map(c -> taskId2 + c.pushNotificationConfig().id())
.map(c -> taskId2 + c.config().id())
.toList();

for (String id : task1Ids) {
Expand Down Expand Up @@ -535,7 +535,7 @@ public void testPaginationOrderingConsistency() {
ListTaskPushNotificationConfigResult result = pushNotificationConfigStore.getInfo(params);

result.configs().forEach(c ->
allConfigIds.add(c.pushNotificationConfig().id()));
allConfigIds.add(c.config().id()));
pageToken = result.nextPageToken();
pageCount++;

Expand Down
Loading
Loading