Skip to content

fix: sanitize internal error messages in transport handlers - #1046

Open
ez-lbz wants to merge 5 commits into
a2aproject:mainfrom
ez-lbz:fix/internal-error-sanitization
Open

fix: sanitize internal error messages in transport handlers #1046
ez-lbz wants to merge 5 commits into
a2aproject:mainfrom
ez-lbz:fix/internal-error-sanitization

Conversation

@ez-lbz

@ez-lbz ez-lbz commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What changed

1. Sanitize internal error messages in the JSON-RPC handler

Problem: JSONRPCHandler converted every unexpected (non-A2AError) exception into new InternalError(t.getMessage()), forwarding the raw exception message — often containing file paths, library names, or class names — to the client in the JSON-RPC error payload (CWE-209, information disclosure / server fingerprinting).

Fix (transport/jsonrpc/src/main/java/org/a2aproject/sdk/transport/jsonrpc/handler/JSONRPCHandler.java):

  • Added a LOGGER (java.util.logging) and a private internalError(Throwable) helper that logs the full exception server-side (Level.SEVERE) and returns new InternalError("Internal error") with a generic, client-safe message.
  • Replaced all 12 new InternalError(...getMessage()) sites (onMessageSend, onMessageSendStream, onSubscribeToTask, cancel/get/list tasks, push-notification config endpoints, extended agent card, and the streaming subscriber onError path).

Fix (transport/jsonrpc/src/test/java/org/a2aproject/sdk/transport/jsonrpc/handler/JSONRPCHandlerTest.java):

  • testOnMessageSendSanitizesUnexpectedException — a mocked RuntimeException with a sensitive message yields an InternalError whose message is "Internal error" and does not contain the sensitive text.

2. Sanitize internal error messages in the REST handler

Fix (transport/rest/src/main/java/org/a2aproject/sdk/transport/rest/handler/RestHandler.java):

  • Added a private internalError(Throwable) helper (logs at SEVERE, returns new InternalError("Internal error")) and replaced all 13 new InternalError(...getMessage()) sites across send/cancel/get/list/subscribe/push-config/agent-card endpoints, including the streaming error paths.

Fix (transport/rest/src/test/java/org/a2aproject/sdk/transport/rest/handler/RestHandlerTest.java):

  • testSendMessageSanitizesInternalError — asserts a 500 response whose message is "Internal error" (no sensitive text leaked).

3. Sanitize internal error messages in the gRPC handler

Problem: GrpcHandler.handleInternalError sent t.getMessage() in the gRPC error description, leaking internal exception details to the client.

Fix (transport/grpc/src/main/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandler.java):

  • handleInternalError now logs the full exception at SEVERE and calls handleError with new InternalError("Internal error"), so the gRPC status description carries only the generic message.

Fix (transport/grpc/src/test/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandlerTest.java):

  • testOnMessageInternalErrorIsSanitized — a mocked RuntimeException yields gRPC INTERNAL with description "Internal error" (no sensitive text).

Note: exceptions that are already A2AError (including InternalError thrown by the request handler) still flow through unchanged — sanitization applies to unexpected non-A2A exceptions. The compat-0.3 module has its own copies of the handlers and was intentionally left unchanged (out of scope).

Testing

  • mvn -pl transport/jsonrpc,transport/grpc,transport/rest test127 tests run, 0 failures, 1 skipped (BUILD SUCCESS): JSON-RPC 49, gRPC 41, REST 37, including the 3 new sanitization regression tests.
  • mvn -pl reference/jsonrpc test198 tests run, 0 failures, 0 skipped (BUILD SUCCESS).

@ehsavoie ehsavoie left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are still some InternalError leaking messages:

  • reference/jsonrpc/src/main/java/.../A2AServerRoutes.java:340 — new InternalError(t.getMessage())
  • reference/rest/src/main/java/.../A2AServerRoutes.java:309, 427, 461, 495, 605, 636, 681, 714 — new InternalError(t.getMessage())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This should be sanitized s well

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This isn't addressed

The reference JSON-RPC and REST route handlers still propagated the raw
exception message to clients via new InternalError(t.getMessage()). Use
the generic InternalError message and log the original throwable so
server-side observability is preserved.
@ez-lbz

ez-lbz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — the transport handlers were fixed but the reference server routes were missed. All 9 remaining new InternalError(t.getMessage()) sites (1 in reference/jsonrpc, 8 in reference/rest) now use the generic InternalError("Internal error"), with the original throwable logged server-side.

Testing: reference/jsonrpc QuarkusA2AJSONRPCVertxTest 54 passed; reference/rest QuarkusA2ARestVertxTest 57 passed (9 skipped).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This isn't addressed

error = new A2AErrorResponse(new JSONParseError(e.getMessage()));
} catch (Throwable t) {
error = new A2AErrorResponse(new InternalError(t.getMessage()));
LOG.error("Failed to process request", t);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe use the same message as in the handlers:
"Internal error while processing request"

import static org.a2aproject.sdk.transport.jsonrpc.context.JSONRPCContextKeys.HEADERS_KEY;
import static org.a2aproject.sdk.transport.jsonrpc.context.JSONRPCContextKeys.METHOD_NAME_KEY;
import static org.a2aproject.sdk.transport.jsonrpc.context.JSONRPCContextKeys.TENANT_KEY;
import org.slf4j.Logger;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe move this down after java imports

response = jsonRestHandler.sendMessage(context, extractTenant(rc), body);
} catch (Throwable t) {
response = jsonRestHandler.createErrorResponse(new InternalError(t.getMessage()));
LOG.error("Failed to process request", t);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe use the same message as in the handlers:
"Internal error while processing request"

import static org.a2aproject.sdk.transport.rest.context.RestContextKeys.METHOD_NAME_KEY;
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import org.slf4j.Logger;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Move this down after the java imports

@ehsavoie

Copy link
Copy Markdown
Collaborator

@ez-lbz some small fixes before I can merge

… messages

- RestHandler: the response-serialization failure path leaked the
  InvalidProtocolBufferException message; sanitize it and log the throwable.
- Reference JSON-RPC/REST routes now log the same message as the transport
  handlers ('Internal error while processing request') and place the
  slf4j imports after the java imports.
@ez-lbz

ez-lbz commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

All five points addressed:

  1. RestHandler — the response-serialization failure path (createSuccessResponse) was still leaking the InvalidProtocolBufferException message; now sanitized to the generic internal error with the throwable logged.
  2. Reference JSON-RPC/rest routes now log the same message as the transport handlers (Internal error while processing request), and the client-facing message stays the generic internal error.
  3. slf4j imports moved after the java imports in both A2AServerRoutes files.

Testing: RestHandlerTest 37 passed; QuarkusA2AJSONRPCVertxTest 54 passed (one run had a transient failure that did not reproduce).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants