fix: stop leaking exception messages to clients via JSON-RPC error data

Tool handler exceptions and anything reaching JsonRpcHandler's blind
rescue had their #message echoed into the error data member, exposing
internals to untrusted clients (CWE-209). Capability errors keep their
client-facing message by re-raising through RequestHandlerError, the
one channel whose message is deliberately surfaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joshua J 2026-08-06 17:47:56 +02:00
commit f379e13b47
No known key found for this signature in database
2 changed files with 23 additions and 14 deletions

View file

@ -135,11 +135,12 @@ module JsonRpcHandler
success_response(id: id, result: result)
rescue MCP::Server::RequestHandlerError => e
handle_request_error(e, id, id_validation_pattern)
rescue StandardError => e
rescue StandardError
# The exception message is deliberately not echoed into `data`: it can carry
# internals that must not reach untrusted clients (CWE-209).
error_response(id: id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::INTERNAL_ERROR,
message: "Internal error",
data: e.message,
})
end
end

View file

@ -560,7 +560,14 @@ module MCP
return
end
Methods.ensure_capability!(method, capabilities)
begin
Methods.ensure_capability!(method, capabilities)
rescue Methods::MissingRequiredCapabilityError => e
# Re-raise through RequestHandlerError, the one channel whose message is
# deliberately surfaced to clients: `JsonRpcHandler`'s blind `rescue StandardError`
# no longer echoes exception messages into the error `data` member (CWE-209).
raise RequestHandlerError.new(e.message, request, error_type: :internal_error, original_error: e)
end
# `initialize` MUST NOT be cancelled (MCP spec 2025-11-25, cancellation item 2),
# so do not track it in the in-flight registry.
@ -864,8 +871,11 @@ module MCP
# `JsonRpcHandler::NO_RESPONSE` per the MCP cancellation spec.
raise
rescue => e
# `e.message` is deliberately not included: it can carry internals (class, method
# and host names) that must not reach untrusted clients (CWE-209). The original
# exception still reaches `configuration.exception_reporter` via `original_error`.
raise RequestHandlerError.new(
"Internal error calling tool #{tool_name}: #{e.message}",
"Internal error calling tool #{tool_name}",
request,
error_type: :internal_error,
original_error: e,