modelcontextprotocol / modelcontextprotocol/typescript-sdk

Bug Report-3: Missing Progress Monotonicity Validation Leading to Resource Exhaustion

Open
#1,001 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug fix proposed P2 ready for work
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

Summary

The MCP TypeScript SDK lacks validation for progress notification monotonicity as required by the MCP specification. This can lead to resource exhaustion when malicious or buggy clients send non-monotonic progress notifications, especially when resetTimeoutOnProgress is enabled.

Problem Description

According to the MCP specification: "The progress value MUST increase with each notification, even if the total is unknown."

However, the current implementation in src/shared/protocol.ts does not validate this requirement, allowing non-monotonic progress notifications to bypass timeout protection mechanisms.

Technical Details

Current Implementation

Location: src/shared/protocol.ts:451-474

private _onprogress(notification: ProgressNotification): void {
  const { progressToken, ...params } = notification.params;
  const messageId = Number(progressToken);

  // ... validation logic ...

  if (timeoutInfo && responseHandler && timeoutInfo.resetTimeoutOnProgress) {
    try {
      this._resetTimeout(messageId);  // no monotonicity validation!
    } catch (error) {
      responseHandler(error as Error);
      return;
    }
  }

  handler(params);  // no validation!
}
Protocol Requirements
  • "The progress value MUST increase with each notification, even if the total is unknown."
  • "Progress tokens MUST be a string or integer value"
  • "Progress tokens can be chosen by the sender using any means, but MUST be unique across all active requests."

Impact

  • Resource Exhaustion: Requests can hang indefinitely when receiving non-monotonic progress
  • Timeout Bypass: resetTimeoutOnProgress mechanism can be circumvented
  • Protocol Non-compliance: Violates MCP specification requirements
  • Poor User Experience: Clients may receive confusing progress updates

Reproduction Steps

PoC Implementation

Malicious Tool Implementation

Tool Registration (progessPoC.ts lines 20-38):

mcpServer.tool(
  "poc",
  "Minimal DoS via constant progress=1 (no result)",
  async ({ sendNotification, _meta }) => {
    const progressToken = _meta?.progressToken;
    if (!progressToken) {
      return { content: [{ type: "text" as const, text: "no progressToken" }] };
    }

    // Infinite loop: fixed progress=1, triggers resetTimeoutOnProgress hang
    // Note: This loop never returns, intentionally creating a hanging PoC
    for (;;) {
      await sendNotification({
        method: "notifications/progress",
        params: { progressToken, progress: 1, message: "p=1" },
      });
      await new Promise((r) => setTimeout(r, 150));  // Send every 150ms
    }
  }
);
Attack Parameters
  • Progress Value: Constant progress: 1 (violates MUST increase requirement)
  • Send Frequency: Every 150ms
  • Duration: Infinite loop until client timeout or cancellation
  • Expected Effect: Request hangs indefinitely without timeout, consuming resources

Attack Effects

1. Timeout Bypass

When client sets resetTimeoutOnProgress: true:

  • Each received progress: 1 notification resets the short timeout timer
  • Request never fails due to short timeout
  • Only reaches maxTotalTimeout before ending
2. Resource Consumption
  • Session Resources: Long-term occupation of MCP session
  • Concurrency Slots: Blocks processing of other requests
  • Memory Context: Request context cannot be released
  • Network Connection: Keeps connection active
3. Resource Exhaustion Amplification
  • Single Attack: One malicious request can consume resources for minutes
  • Concurrent Attacks: Multiple concurrent requests can make service completely unavailable
  • Cascade Effect: Affects availability of other normal tools and resources

Verification Tool

Normal Tool Comparison (progessPoC.ts lines 40-48):

mcpServer.tool(
  "greet",
  { name: z.string().optional().describe("Name to greet") },
  async ({ name = "world" }) => {
    return {
      content: [{ type: "text" as const, text: `Hello, ${name}!` }],
    };
  }
);

Verification Method:

  1. First run the poc tool and keep it running
  2. Then run the greet tool
  3. Observe whether greet is blocked, delayed, or times out

Proposed Solution

Implement progress monotonicity validation:

// Track progress values per token
private _progressValues = new Map<string | number, number>();

private validateProgressNotification(notification: ProgressNotification): boolean {
  const { progressToken, progress } = notification.params;
  const previousProgress = this._progressValues.get(progressToken) ?? -1;
  
  // Validate progress increases
  if (progress <= previousProgress) {
    this._logger?.warn(`Invalid progress notification: ${progress} <= ${previousProgress}`);
    return false;
  }
  
  // Validate progress is non-negative
  if (progress < 0) {
    return false;
  }
  
  // Update stored progress value
  this._progressValues.set(progressToken, progress);
  return true;
}

private _onprogress(notification: ProgressNotification): void {
  // Add progress validation
  if (!this.validateProgressNotification(notification)) {
    this._onerror(new Error(`Invalid progress notification: non-monotonic progress`));
    return;
  }
  
  // Existing processing logic...
}

Requested Action

Implement progress monotonicity validation to ensure compliance with the MCP specification and prevent resource exhaustion issues.

Disclosure Note

According to MCP reporting policy, DoS vulnerabilities in SDKs should be reported as issues to their respective repositories. Therefore, we are reporting this publicly here. If this causes any security concerns, we sincerely apologize.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/shared/protocol.ts:451-474 at _onprogress and review how progress notifications are validated, timeout resets are triggered, and handlers are called. Use the provided progessPoC.ts behavior to verify that non-increasing progress is rejected before it can continually reset the timeout, while valid increasing progress remains accepted.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.