ruby-sdk/lib/mcp
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Koichi ITO 3546660531 Add a Session-Ownership Hook to StreamableHTTPTransport
## Motivation and Context

`StreamableHTTPTransport` issues a random `SecureRandom.uuid` session ID and validates
an incoming request by session existence and idle timeout only, with no binding to a client identity.
An attacker who obtains a victim's session ID (network sniffing, logs, etc.) can therefore POST
a `tools/call` on that session and the server executes it, silently poisoning the victim's session.
The previously shipped 409 fix guards the GET SSE stream slot but does nothing for the POST write path,
so this is not covered by it.

Binding a session to a user is fundamentally the deploying application's responsibility:
the transport hands the server only the JSON-RPC body string and never receives the authenticated principal
(`Authorization`/`Origin` headers do not reach handlers), and the MCP spec frames session-to-user binding as
a SHOULD. The Python, Go, and C# SDKs bind automatically only when an authenticated identity is present.
This gives the Ruby SDK the seam to do the same:

- A `session_request_validator` constructor option, called as `->(request, session_id) { true | false }`
  on every non-`initialize` POST, GET, and DELETE against an existing session; a falsy return rejects
  with HTTP 403. The gate is applied uniformly to regular requests, notifications, and client responses,
  not just tool calls - otherwise a stolen session ID could POST `notifications/cancelled` to cancel
  a victim's in-flight request, or a forged client response, by taking a branch the gate did not cover.
  `initialize` is exempt because it establishes the session. An application that has an authenticated principal
  uses the validator to enforce that the caller owns the session; without one, ownership is not enforced.
- A limited built-in Origin-consistency check as defense in depth (not authentication): the `Origin` header
  is recorded at `initialize` and a later request whose `Origin` differs is rejected, but only when
  both are present. A non-browser client that omits `Origin` (curl, a script - i.e. the advisory's own PoC)
  is not stopped by this check; it only raises the bar against a browser-based cross-origin attacker.
  Enforcing ownership against a determined attacker requires the validator.

Stateless mode retains no sessions, so neither check applies to it.

## How Has This Been Tested?

New tests in `test/mcp/server/transports/streamable_http_transport_test.rb`: a POST whose Origin differs
from `initialize` is rejected with 403; a matching Origin and a no-Origin request both succeed;
a custom `session_request_validator` both rejects (403) and allows (200) and receives the request and session ID;
the validator also gates notification (`notifications/cancelled`) and client-response POSTs, not just regular requests;
and stateless mode never consults the validator.

## Breaking Changes

None. `session_request_validator` defaults to `nil`, and the built-in Origin check only rejects when both
the session and the request carry an `Origin` and they differ, so existing applications are unaffected.
2026-07-07 21:41:29 +09:00
..
client Bound stdio frame reads with max_line_bytes 2026-07-05 21:30:28 +09:00
prompt Add _meta field to resource, content, and result classes 2026-04-11 15:08:55 +09:00
resource Add _meta field to resource, content, and result classes 2026-04-11 15:08:55 +09:00
server Add a Session-Ownership Hook to StreamableHTTPTransport 2026-07-07 21:41:29 +09:00
tool Conform Tool Schemas to JSON Schema 2020-12 per SEP-2106 2026-06-19 21:36:55 +09:00
annotations.rb Add audience role validation for MCP::Annotations per MCP specification 2026-06-22 01:13:17 +09:00
cancellation.rb Support notifications/cancelled per MCP specification 2026-05-03 04:02:22 +09:00
cancelled_error.rb Support notifications/cancelled per MCP specification 2026-05-03 04:02:22 +09:00
client.rb Support client-side notifications/cancelled per MCP specification 2026-06-23 12:39:53 +09:00
configuration.rb Default missing MCP-Protocol-Version to 2025-03-26 in StreamableHTTPTransport 2026-06-07 10:18:33 +09:00
content.rb Add _meta field to resource, content, and result classes 2026-04-11 15:08:55 +09:00
icon.rb Support icons parameter 2025-12-30 05:35:22 +09:00
instrumentation.rb Add around_request hook for request instrumentation 2026-04-11 13:54:47 +09:00
logging_message_notification.rb logging_message_notification: Remove unused require 2026-02-05 16:05:08 -05:00
methods.rb Fix an incorrect result: null response to an id-bearing notification message 2026-07-01 01:55:15 +09:00
progress.rb Support POST response SSE streams for server-to-client messages 2026-04-04 19:20:31 +09:00
prompt.rb Use autoload to defer loading of unused subsystems 2026-03-12 14:08:27 +09:00
resource.rb Add annotations field to MCP::Resource and MCP::ResourceTemplate per MCP specification 2026-06-14 16:05:25 +09:00
resource_template.rb Add annotations field to MCP::Resource and MCP::ResourceTemplate per MCP specification 2026-06-14 16:05:25 +09:00
server.rb Merge pull request #435 from koic/fix_result_null_for_id_bearing_notification 2026-07-02 07:40:40 +09:00
server_context.rb docs: annotate Roots, Sampling, and Logging as deprecated per SEP-2577 2026-06-27 14:02:27 +02:00
server_session.rb docs: annotate Roots, Sampling, and Logging as deprecated per SEP-2577 2026-06-27 14:02:27 +02:00
string_utils.rb Fix ReDoS vulnerability in StringUtils#underscore 2026-01-09 01:14:52 +09:00
tool.rb Use autoload to defer loading of unused subsystems 2026-03-12 14:08:27 +09:00
trace_context.rb Support W3C Trace Context Propagation via _meta per SEP-414 2026-06-12 14:36:24 +09:00
transport.rb Auto-set server.transport in Transport#initialize 2026-04-09 12:08:55 +09:00
version.rb Release 0.22.0 2026-06-27 02:54:30 +09:00