Skip to content

Conversation

ezynda3
Copy link
Contributor

@ezynda3 ezynda3 commented Oct 2, 2025

Description

This PR fixes a security vulnerability where tools could be invoked without proper session initialization. The InsecureStatefulSessionIdManager was only validating session ID format but not checking if the session actually existed, allowing any well-formatted fake session ID to bypass authentication.

Fixes #579

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • MCP spec compatibility implementation
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Tests only (no functional changes)
  • Other (please describe):

Changes Made

Core Implementation

  • Modified InsecureStatefulSessionIdManager to track active sessions using sync.Map
  • Added sessions map to store generated session IDs
  • Added terminated map to track terminated sessions
  • Updated Generate() to store session IDs when created
  • Updated Validate() to check session existence in addition to format validation
  • Updated Terminate() to properly mark sessions as terminated and remove from active sessions

Testing

  • Added TestStreamableHTTP_SessionValidation with comprehensive test cases:
    • Reject tool calls with fake but well-formatted session IDs (returns 400)
    • Reject tool calls with malformed session IDs (returns 400)
    • Accept tool calls with valid session IDs from initialization (returns 200)
    • Reject tool calls with terminated session IDs (returns 404)
  • Added TestInsecureStatefulSessionIdManager for unit testing the session manager:
    • Session generation and validation
    • Rejection of non-existent sessions
    • Rejection of malformed session IDs
    • Proper termination handling
    • Concurrent session generation safety
  • Updated TestStreamableHTTPServer_SamplingErrorHandling to use stateless mode for compatibility

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly

Additional Information

Security Impact

Before this fix, an attacker could invoke tools using any well-formatted session ID like mcp-session-ffffffff-ffff-ffff-ffff-ffffffffffff without going through the initialization flow. This fix ensures that only session IDs that were actually generated by the server are accepted.

Verification

The original exploit from issue #579 now properly returns a 400 Bad Request with "Invalid session ID" error message instead of executing the tool.

All existing tests pass, demonstrating backward compatibility with legitimate use cases.

Summary by CodeRabbit

  • New Features
    • Added configurable stateless mode for the HTTP streaming server.
    • Introduced stateful session lifecycle: initialize, validate, and terminate sessions with clear outcomes.
  • Behavior Changes
    • Requests with invalid or malformed session IDs now return 400.
    • Requests for terminated or unknown sessions now return 404.
    • Improved reliability and concurrency safety for session handling.

- Modified InsecureStatefulSessionIdManager to track active sessions using sync.Map
- Added session existence validation in addition to format validation
- Implemented proper session termination tracking
- Added comprehensive regression tests for session validation scenarios
- Updated sampling tests to use stateless mode for compatibility

This fixes a security issue where tools could be invoked with any well-formatted
session ID without going through proper initialization, allowing unauthorized access.
Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

Walkthrough

Implements stateful session tracking in the HTTP server by adding internal maps for active and terminated sessions to InsecureStatefulSessionIdManager, updating Generate/Validate/Terminate logic. Updates server construction to accept a WithStateLess option. Expands tests to cover session lifecycle validation, termination behavior, and stateless construction in sampling tests.

Changes

Cohort / File(s) Summary
Core server and session management
server/streamable_http.go
InsecureStatefulSessionIdManager gains sessions and terminated maps, updates Generate to register IDs, Validate to check prefix/UUID and existence/termination, and Terminate to require and mark existing sessions. Updates NewStreamableHTTPServer to accept options, including WithStateLess(bool).
Sampling server tests (stateless option usage)
server/streamable_http_sampling_test.go
Constructs server with WithStateLess(true); replaces specific session IDs with placeholders; retains status expectations.
HTTP server tests (session lifecycle)
server/streamable_http_test.go
Adds tests for invalid/malformed session IDs (400), valid initialized session (200), post-termination access (404), and concurrency/state checks for InsecureStatefulSessionIdManager.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • pottekkat
  • rwjblue-glean

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change of preventing tools from being invoked without a valid session initialization, and it directly reflects the main security fix introduced in the pull request.
Linked Issues Check ✅ Passed The implementation now enforces session existence checks in Generate, Validate, and Terminate and returns appropriate errors for fake, malformed, or terminated session IDs, and comprehensive tests cover all required scenarios, fully addressing the objectives of issue [#579].
Out of Scope Changes Check ✅ Passed All modifications, including the stateful session manager enhancements, the new stateless mode option for the streaming server, and corresponding test updates, align with the security objectives and PR goals without introducing unrelated changes.
Description Check ✅ Passed The pull request description follows the repository template by including a concise description with “Fixes #579,” a clear Type of Change section, a completed Checklist, and an Additional Information section detailing security impact and verification steps.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/session-validation-security

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

github-actions bot commented Oct 2, 2025

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/mark3labs/mcp-go/server 71.70% (+1.13%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/mark3labs/mcp-go/server/streamable_http.go 63.61% (+5.65%) 393 (+10) 250 (+28) 143 (-18) 👍

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/mark3labs/mcp-go/server/streamable_http_sampling_test.go
  • github.com/mark3labs/mcp-go/server/streamable_http_test.go

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61b5d9e and a0f4e56.

📒 Files selected for processing (3)
  • server/streamable_http.go (1 hunks)
  • server/streamable_http_sampling_test.go (3 hunks)
  • server/streamable_http_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/streamable_http_test.go (4)
mcp/tools.go (3)
  • NewTool (679-701)
  • CallToolRequest (54-58)
  • CallToolResult (40-51)
mcp/utils.go (1)
  • NewToolResultText (271-280)
server/streamable_http.go (2)
  • NewTestStreamableHTTPServer (1060-1064)
  • InsecureStatefulSessionIdManager (1021-1024)
server/constants.go (1)
  • HeaderKeySessionID (5-5)
server/streamable_http_sampling_test.go (1)
server/streamable_http.go (2)
  • NewStreamableHTTPServer (152-167)
  • WithStateLess (45-51)

Comment on lines 1050 to 1056
func (s *InsecureStatefulSessionIdManager) Terminate(sessionID string) (isNotAllowed bool, err error) {
if _, exists := s.sessions.Load(sessionID); !exists {
return false, fmt.Errorf("session not found: %s", sessionID)
}
s.terminated.Store(sessionID, true)
s.sessions.Delete(sessionID)
return false, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make session termination idempotent

Terminate now returns an error once the session has been deleted from sessions, so a client that retries DELETE /... after succeeding immediately hits a 500 (handleDelete turns this error into Session termination failed). Termination endpoints must be idempotent—network retries shouldn’t look like server failures. Please allow already-terminated IDs to short‑circuit as success (or map them to a 404) before returning an error.

 func (s *InsecureStatefulSessionIdManager) Terminate(sessionID string) (isNotAllowed bool, err error) {
-	if _, exists := s.sessions.Load(sessionID); !exists {
-		return false, fmt.Errorf("session not found: %s", sessionID)
-	}
-	s.terminated.Store(sessionID, true)
-	s.sessions.Delete(sessionID)
+	if _, exists := s.sessions.Load(sessionID); !exists {
+		if _, terminated := s.terminated.Load(sessionID); terminated {
+			return false, nil
+		}
+		return false, fmt.Errorf("session not found: %s", sessionID)
+	}
+	s.terminated.Store(sessionID, true)
+	s.sessions.Delete(sessionID)
 	return false, nil
 }
🤖 Prompt for AI Agents
In server/streamable_http.go around lines 1050–1056, Terminate currently returns
an error when the session ID is not found in s.sessions, causing retries to
surface as server failures; change the logic to make termination idempotent by
first checking whether the session is already marked terminated (or if not
found, consult s.terminated) and if so return (false, nil) to indicate success,
otherwise mark terminated and delete the session and return (false, nil); ensure
you do not treat a missing sessions entry as an internal error when the session
is already terminated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

bug: Tools can be invoked without initialization
1 participant