CVE-2026-52870
Insecure task handlers in MCP SDK allow any client to access/cancel others' tasks.
- CVSS 7.6
- CWE-862
- Authentication, Authorization, and Session Management
- Remote
The MCP Python SDK, called mcp on PyPI, is a Python implementation of the Model Context Protocol (MCP). From 1.23.0 until 1.27.2, default handlers installed by server.experimental.enable_tasks() for tasks/list, tasks/get, tasks/result, and tasks/cancel operate only on task identifiers without recording the session that created each task, allowing any connected client to enumerate, read results from, consume messages for, or cancel other clients' tasks. This issue is fixed in version 1.27.2.
- CWE
- CWE-862
- CVSS base score
- 7.6
- Published
- 2026-07-15
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Insecure Direct Object References (IDOR)
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- mcp
- Fixed by upgrading
- Yes
Solution
Upgrade `mcp` to version 1.27.2 or later.
Vulnerable code sample
import uuid
from typing import Dict, Any
# This vulnerable implementation uses a simple global dictionary for task storage.
# The crucial flaw is that it does not associate a task with the session
# that created it, allowing any session to interact with any task.
TASK_STORAGE: Dict[str, Dict[str, Any]] = {}
class VulnerableMCPServer:
"""
A simplified representation of the server behavior before the fix.
The handlers for task operations lack session-based authorization.
"""
def __init__(self):
# In a real scenario, this would be a long-lived object.
# It operates on the global TASK_STORAGE.
self.tasks = TASK_STORAGE
def experimental_enable_tasks(self):
"""
Conceptually enables the vulnerable task handlers. In the real SDK,
this would register handlers for specific network endpoints.
"""
# The methods below simulate the logic of the vulnerable handlers.
print("Vulnerable task handlers for list, get, result, and cancel are enabled.")
def create_task(self, session_id: str, data: Any) -> str:
"""
Simulates a client from a specific session creating a task.
"""
task_id = f"task_{uuid.uuid4().hex[:8]}"
# FLAW: The creating session_id is received but NOT stored with the task.
self.tasks[task_id] = {
"status": "running",
"result": None,
"data": data,
}
return task_id
# --- VULNERABLE HANDLER IMPLEMENTATIONS ---
# The following methods simulate the default handlers. They only use 'task_id'
# and have no concept of which session is making the request.
def handle_list_tasks(self) -> list:
"""
VULNERABLE: Any client can list all task IDs on the server.
This handler does not filter by session.
"""
return list(self.tasks.keys())
def handle_get_task(self, task_id: str) -> Dict[str, Any]:
"""
VULNERABLE: Any client can get the details of any task if they
know or can guess the task_id.
"""
if task_id not in self.tasks:
raise KeyError("Task not found")
return self.tasks[task_id]
def handle_get_result(self, task_id: str) -> Any:
"""
VULNERABLE: Any client can retrieve the result of any task.
"""
if task_id not in self.tasks:
raise KeyError("Task not found")
# Simulate task completion for demonstration
if self.tasks[task_id].get("result") is None:
self.tasks[task_id]["result"] = f"Sensitive result for {task_id}"
return self.tasks[task_id]["result"]
def handle_cancel_task(self, task_id: str) -> Dict[str, str]:
"""
VULNERABLE: Any client can cancel any other client's task.
"""
if task_id not in self.tasks:
raise KeyError("Task not found")
task = self.tasks[task_id]
if task["status"] == "running":
task["status"] = "cancelled"
return {"status": "success", "message": f"Task {task_id} has been cancelled."}
return {"status": "noop", "message": "Task was not in a running state."}Patched code sample
import uuid
from typing import Dict, Any, Optional, List
# The provided CVE is fictitious. This code demonstrates a conceptual fix
# for the described vulnerability: lack of session-based authorization for tasks.
#
# The vulnerability: Task handlers operated only on `task_id`, allowing any
# client to access or cancel any other client's tasks.
#
# The fix: Associate a `session_id` with each task upon creation and check
# this `session_id` on every subsequent operation to ensure the requesting
# client is the authorized owner of the task.
class FixedTaskServer:
"""
A simplified server demonstrating the fixed logic where task operations
are correctly scoped to the session that created them.
"""
def __init__(self):
# In a real app, this would be a more robust, persistent task store.
# Key: task_id, Value: Dict containing task data and owner's session_id.
self._tasks: Dict[str, Dict[str, Any]] = {}
def _authorize_and_get_task(self, session_id: str, task_id: str) -> Optional[Dict[str, Any]]:
"""
Helper method embodying the security fix.
It retrieves a task only if the requesting session is the owner.
"""
task = self._tasks.get(task_id)
if task and task.get("session_id") == session_id:
return task
# Return None if task not found or session ID does not match, denying access.
return None
def create_task(self, session_id: str, task_data: Any) -> str:
"""Creates a task and securely associates it with the creating session."""
task_id = str(uuid.uuid4())
self._tasks[task_id] = {
"data": task_data,
"result": None,
"status": "pending",
"session_id": session_id # CRITICAL: Record the owner.
}
return task_id
def get_task_result(self, requesting_session_id: str, task_id: str) -> Optional[Any]:
"""
Gets a task result, but only if the requesting session is the owner.
The vulnerable version would have omitted the ownership check.
"""
task = self._authorize_and_get_task(requesting_session_id, task_id)
if task:
return task.get("result")
return None
def cancel_task(self, requesting_session_id: str, task_id: str) -> bool:
"""
Cancels a task, but only if the requesting session is the owner.
The vulnerable version would have allowed any client to cancel any task.
"""
task = self._authorize_and_get_task(requesting_session_id, task_id)
if task and task["status"] == "pending":
task["status"] = "cancelled"
return True
return False
def list_tasks(self, requesting_session_id: str) -> List[str]:
"""
Lists tasks, but only returns tasks owned by the requesting session.
The vulnerable version would have returned all tasks to all clients.
"""
return [
tid for tid, t_data in self._tasks.items()
if t_data.get("session_id") == requesting_session_id
]Cite this entry
@misc{vaitp:cve202652870,
title = {{Insecure task handlers in MCP SDK allow any client to access/cancel others' tasks.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-52870},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-52870/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
