Skip to content

Commit 68d1762

Browse files
fix(client): allowlist inputRequest methods serviced by MRTR loop (sampling/elicitation/roots/ping only)
1 parent 7921183 commit 68d1762

1 file changed

Lines changed: 17 additions & 1 deletion

File tree

packages/client/src/client/client.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ export type ClientOptions = ProtocolOptions & {
193193

194194
/**
195195
* SEP-2322: maximum number of incomplete-result rounds before failing. Each round
196-
* services the server's `inputRequests` via local handlers and re-sends with
196+
* services the server's `inputRequests` via local handlers (sampling, elicitation,
197+
* roots, ping only; other methods are rejected) and re-sends with
197198
* `params.{inputResponses, requestState}`. Prevents unbounded looping on a server
198199
* that returns `incomplete` forever.
199200
*
@@ -205,9 +206,18 @@ export type ClientOptions = ProtocolOptions & {
205206
const DEFAULT_MRTR_MAX_ROUNDS = 16;
206207

207208
/** SEP-2322 client-side detection. The server signals it cannot complete without client input. */
209+
/**
210+
* Methods the SEP-2322 retry loop will service from `inputRequests`. The server cannot
211+
* use this channel to invoke arbitrary client handlers (e.g. a custom-method handler the
212+
* application registered for unrelated purposes); only the spec-defined client-side
213+
* request methods are dispatched.
214+
*/
215+
const ALLOWED_INPUT_REQUEST_METHODS: ReadonlySet<string> = new Set(['sampling/createMessage', 'elicitation/create', 'roots/list', 'ping']);
216+
208217
function isIncompleteResult(r: unknown): r is IncompleteResult {
209218
if (typeof r !== 'object' || r === null) return false;
210219
const o = r as { resultType?: unknown; inputRequests?: unknown };
220+
// (deliberately narrow guard; the loop body validates method against ALLOWED_INPUT_REQUEST_METHODS)
211221
if (o.resultType !== 'incomplete') return false;
212222
if (o.inputRequests === undefined) return true;
213223
return typeof o.inputRequests === 'object' && o.inputRequests !== null && !Array.isArray(o.inputRequests);
@@ -1159,6 +1169,12 @@ export class Client extends Protocol<ClientContext> {
11591169
const out: Record<string, unknown> = {};
11601170
for (const [key, ir] of Object.entries(reqs)) {
11611171
signal?.throwIfAborted();
1172+
if (!ALLOWED_INPUT_REQUEST_METHODS.has(ir.method)) {
1173+
throw new ProtocolError(
1174+
ProtocolErrorCode.InvalidRequest,
1175+
`inputRequest method '${ir.method}' is not a client-serviceable method`
1176+
);
1177+
}
11621178
const resp = await this._serviceInboundRequest(
11631179
{ jsonrpc: '2.0', id: `mrtr:${key}`, method: ir.method, params: ir.params },
11641180
signal

0 commit comments

Comments
 (0)