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
3 changed files with 23 additions and 14 deletions

View file

@ -310,7 +310,6 @@ describe JsonRpcHandler do
assert_rpc_error expected_error: {
code: -32603,
message: "Internal error",
data: "Something bad happened",
}
assert_equal 1, @response[:id]
assert_nil @response[:result]
@ -452,7 +451,6 @@ describe JsonRpcHandler do
assert_rpc_error expected_error: {
code: -32603,
message: "Internal error",
data: "Something bad happened",
}
end

View file

@ -545,7 +545,7 @@ module MCP
assert_nil response[:result]
assert_equal(-32603, response[:error][:code])
assert_equal "Internal error", response[:error][:message]
assert_match(/Internal error calling tool tool_with_required_context: /, response[:error][:data])
assert_equal "Internal error calling tool tool_with_required_context", response[:error][:data]
end
test "call_tool_with_args correctly detects server_context parameter presence" do

View file

@ -884,7 +884,7 @@ module MCP
assert_nil response[:result]
assert_equal(-32603, response[:error][:code])
assert_equal "Internal error", response[:error][:message]
assert_match(/Internal error calling tool tool_that_raises: /, response[:error][:data])
assert_equal "Internal error calling tool tool_that_raises", response[:error][:data]
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises", tool_arguments: { message: "test" }, error: :internal_error })
end
@ -941,7 +941,7 @@ module MCP
assert_nil response[:result]
assert_equal(-32603, response[:error][:code])
assert_equal "Internal error", response[:error][:message]
assert_match(/Internal error calling tool tool_that_raises: /, response[:error][:data])
assert_equal "Internal error calling tool tool_that_raises", response[:error][:data]
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises", tool_arguments: { message: "test" }, error: :internal_error })
end
@ -972,7 +972,7 @@ module MCP
assert_nil response[:result]
assert_equal(-32603, response[:error][:code])
assert_equal "Internal error", response[:error][:message]
assert_match(/Internal error calling tool tool_with_faulty_schema: Unexpected schema error/, response[:error][:data])
assert_equal "Internal error calling tool tool_with_faulty_schema", response[:error][:data]
end
test "#handle tools/call returns JSON-RPC error for unknown tool" do
@ -1727,7 +1727,7 @@ module MCP
assert_equal(["tool failure", "around ensure boom"], reported)
assert_equal(JsonRpcHandler::ErrorCode::INTERNAL_ERROR, response[:error][:code])
assert_equal("around ensure boom", response[:error][:data])
assert_nil(response[:error][:data])
end
test "#handle reports the same exception object reused across requests on every call" do
@ -1779,7 +1779,7 @@ module MCP
response = server.handle(request)
assert_equal([frozen_error], reported)
assert_includes(response[:error][:data], "frozen failure")
assert_equal("Internal error calling tool frozen_tool", response[:error][:data])
end
test "#handle still reports via exception_reporter when around_request swallows the tool failure" do
@ -2635,7 +2635,7 @@ module MCP
assert_nil response[:result]
assert_equal(-32603, response[:error][:code])
assert_equal "Internal error", response[:error][:message]
assert_match(/Internal error calling tool invalid_structured_content_tool: Invalid result:/, response[:error][:data])
assert_equal "Internal error calling tool invalid_structured_content_tool", response[:error][:data]
end
test "tools/call returns JSON-RPC error when output schema validation is enabled and structuredContent is missing" do
@ -2664,7 +2664,7 @@ module MCP
assert_nil response[:result]
assert_equal(-32603, response[:error][:code])
assert_equal "Internal error", response[:error][:message]
assert_match(/Internal error calling tool missing_structured_content_tool: Invalid result:/, response[:error][:data])
assert_equal "Internal error calling tool missing_structured_content_tool", response[:error][:data]
end
test "tools/call skips output schema validation for error responses" do