· 11 min read · Nadeera Silva
MCP Vulnerability Roundup: November 2026 Server CVEs
17 MCP server and SDK CVEs from early November 2026, the bug patterns behind them, and a tested path and SSRF guard plus a hardening checklist.

Between 1 and 10 November 2026, the National Vulnerability Database published around 30 CVE records matching the keyword MCP. If you run an MCP server, or an app that exposes MCP tools, the obvious question is which mcp vulnerability affects you and what to do about it. I went through the records and the original advisories, kept the ones that hit real MCP code paths, and grouped them by bug class. The same six or seven mistakes keep coming back, so for MCP security the hardening checklist at the end is more useful than any single patch.
What happened in early November
Three things landed in the same week.
- Official SDK fixes reached NVD. The MCP TypeScript SDK published a set of advisories in October, and the CVE records went live on NVD on 5 November. They cover cross-client message delivery, token audience checks, redirect leaks and a memory exhaustion bug. This is the closest thing to an "Anthropic MCP vulnerability" in this batch: the bugs are in the official SDK, not in the protocol spec, and they are fixed in
@modelcontextprotocol/sdk1.32.0 and the 2.3.0 packages. - Vendor MCP servers shipped fixes. AWS published bulletin 2026-164-AWS on 2 November for a path traversal in the AWS Serverless MCP Server. IBM's ContextForge CVE reached NVD on 8 November (the IBM bulletin is dated 27 October), and the Dynatrace MCP server CVEs on 9 November.
- Apps with an MCP endpoint bolted on got caught. CMSs, dashboards, scrapers and databases added MCP tools that skipped checks their normal HTTP API already had.
I covered the critical FastMCP Prefab UI sandbox escape separately in CVE-2026-120338: check and fix Prefab UI, so it isn't repeated here.
MCP vulnerability list: November 2026
| CVE | Package | Affected | Fixed | CVSS | Bug class |
|---|---|---|---|---|---|
| CVE-2026-111770 | @modelcontextprotocol/server | 2.0.0 to 2.2.0 | 2.3.0 (plus code change) | 7.7 | Shared transport leak |
| CVE-2026-111768 | @modelcontextprotocol/sdk, /server | sdk 1.3.0 to 1.31.x, server 2.0.0 to 2.2.0 | sdk 1.32.0, server 2.3.0 (plus config) | 7.5 | Memory exhaustion DoS |
| CVE-2026-111771 | @modelcontextprotocol/sdk, /server, /express | sdk 1.6.0 to 1.31.x, server 2.0.0 to 2.2.0 | sdk 1.32.0, server 2.3.0, express 2.0.2 (plus config) | 6.8 | Token audience not checked |
| CVE-2026-111769 | @modelcontextprotocol/sdk, /client | sdk below 1.32.0, client 2.0.0 to 2.2.x | sdk 1.32.0, client 2.3.0 | 6.5 | Headers and tokens leak on redirect |
| CVE-2026-121419 | SurrealDB | 3.1.0 to 3.2.x | 3.3.0 | 8.1 | Session hijack |
| CVE-2026-123272 | mark3labs/mcp-go | 0.48.0 to 1.2.0 | 1.2.1 | 8.0 | OAuth resource confusion |
| CVE-2026-123271 | ChatCLI | 1.159.0 to 1.215.1 | 1.215.2 | 8.0 | OAuth resource confusion |
| CVE-2026-123811 | @dynatrace-oss/dynatrace-mcp-server | up to 1.8.7 | 2.0.0 | 7.5 | No auth in HTTP mode |
| CVE-2026-102610 | fast-agent-mcp | up to 0.7.20 | 0.7.21 | 6.4 | No auth, shell exec |
| CVE-2026-123022 | AgentScope | up to 2.0.11 | none yet | 7.3 | Command injection |
| CVE-2026-116635 | awslabs.aws-serverless-mcp-server | below 0.2.1 | 0.2.1 | 6.1 | Path traversal |
| CVE-2026-102943 | Dagu | up to 2.10.7 | 2.11.0 | 6.5 | Path traversal, authz bypass |
| CVE-2026-121458 | Instatic | below 0.0.21 | 0.0.21 | 6.5 | Tool skips app authz |
| CVE-2026-120450 | LaraDashboard | 1.4.0 to 1.4.8 | 1.4.9 | 4.3 | Tool skips app authz |
| CVE-2026-18484 | IBM ContextForge MCP Gateway | 1.0.0 to 1.0.11 | 1.1.0 | 6.5 | Tool name collision DoS |
| CVE-2026-122140 | Trawl | 1.5.0 to 1.9.3 | 1.10.0 | 5.3 | SSRF via IPv4-mapped IPv6 |
| CVE-2026-120492 | Chainlit | 2.12.0 | none yet | 5.4 | SSRF allowlist bypass |
The recurring bug patterns

Path traversal from tool input
The AWS Serverless MCP Server built a file path from the project name argument, so ../ or an absolute path could read JSON files outside the deployment status folder, and overwrite them when --enable-write was on. Dagu's MCP dagu_read operation called the spec loader directly and skipped the HTTP middleware that had fixed the same traversal earlier. The lesson: a model fills in tool arguments, and anything that can steer the model (a README, a ticket, a web page) can steer those arguments.
Tools that skip the app's own authorization
Instatic is the clearest example. A low-privilege user got a 404 from the normal REST endpoint for another user's draft, then read the same row through the content_get_document MCP tool. The capability gate checked that the caller had content.edit.own, but nobody checked who owned the row. LaraDashboard let a user with read-only template access send arbitrary email through an MCP tool. When you add MCP to an existing app, every tool is a new API endpoint and needs the same object-level checks.
SSRF and egress bypasses
Trawl's MCP URL filter matched private IPv4-mapped addresses only in dotted form (::ffff:127.0.0.1). The WHATWG URL parser normalises that to hex (::ffff:7f00:1), so every spelling slipped through, including through redirects. Chainlit's allowlist check didn't reject ;, so ..; path segments escaped a path-scoped grant on servers that strip semicolon parameters. Both are string checks where a numeric check on the resolved address was needed.
MCP OAuth token and resource confusion
In mcp-go and ChatCLI, the client trusted the resource value from the server's Protected Resource Metadata. A malicious MCP server could name a different resource on the same authorization server, get the user to consent, and replay the token there. On the server side, the TypeScript SDK's bearer auth (CVE-2026-111771) didn't check the token audience, so a token minted for another service was accepted.
Session hijack and binding
SurrealDB bound each MCP session to a fingerprint built from the access method name, not the user. Any user of the same access method who learned a victim's mcp-session-id could run tools with the victim's roles. A session ID is not a credential, and the SurrealDB code even said so in a comment; the check just compared the wrong value.
Shared transport leaks
In @modelcontextprotocol/server 2.0.0 to 2.2.0, apps that created one transport or one McpServer at startup and shared it across HTTP clients could deliver one client's responses to another. Replies were matched by JSON-RPC id, and SDK clients start at 0, so collisions happen in normal use. The README examples showed that pattern, which is why it was common. tsParticles' MCP server had the same shape: the first client to initialise claimed the shared transport and locked everyone else out.
Command execution and open listeners
AgentScope passed a user-controlled command to subprocess.Popen through its MCP workspace API. fast-agent's serve bound to 0.0.0.0:8000 with no auth, and with --shell that meant remote command execution. The Dynatrace MCP server accepted unauthenticated tools/call in HTTP mode and ran them with the server's own Dynatrace credentials.
A guard for file paths and outbound URLs
Most of the tool-level bugs above come down to two checks: keep paths inside a root, and only connect to public addresses. Here's a small Python module I use as a starting point in MCP tool handlers. It needs Python 3.9 or newer.
"""Two guards for MCP tool handlers: keep file paths inside a root, keep URLs public."""
import ipaddress
import socket
from pathlib import Path
from urllib.parse import urlsplit
class ToolInputError(ValueError):
pass
def safe_path(root: str, user_path: str) -> Path:
"""Resolve user_path under root. Rejects '..', absolute paths and symlink escapes."""
base = Path(root).resolve()
# Path("/srv") / "/etc/passwd" == Path("/etc/passwd"), so resolve and check containment.
target = (base / user_path).resolve()
if not target.is_relative_to(base):
raise ToolInputError(f"path escapes the allowed root: {user_path!r}")
return target
def _is_public(ip: ipaddress._BaseAddress) -> bool:
# ::ffff:7f00:1 is 127.0.0.1 written as IPv6. Check the IPv4 form too.
if isinstance(ip, ipaddress.IPv6Address) and ip.ipv4_mapped:
ip = ip.ipv4_mapped
return ip.is_global and not ip.is_multicast
def check_url(url: str, allowed_schemes=("https",)) -> list[str]:
"""Return the public IPs a URL resolves to, or raise. Connect to these IPs only."""
parts = urlsplit(url)
if parts.scheme not in allowed_schemes or not parts.hostname:
raise ToolInputError(f"scheme or host not allowed: {url!r}")
if ";" in parts.path or "\\" in parts.path:
raise ToolInputError(f"suspicious characters in path: {url!r}")
try:
infos = socket.getaddrinfo(parts.hostname, parts.port or 443, proto=socket.IPPROTO_TCP)
except socket.gaierror as exc:
raise ToolInputError(f"cannot resolve {parts.hostname!r}") from exc
ips = sorted({info[4][0] for info in infos})
for raw in ips:
if not _is_public(ipaddress.ip_address(raw.split("%")[0])):
raise ToolInputError(f"{parts.hostname!r} resolves to non-public {raw}")
return ipsAnd the tests, which pass on Python 3.13 (python3 test_mcp_guards.py, 4 tests OK):
import os
import tempfile
import unittest
from mcp_guards import ToolInputError, check_url, safe_path
class SafePathTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.root = os.path.join(self.tmp.name, "projects")
os.makedirs(self.root)
os.symlink("/etc", os.path.join(self.root, "link"))
def tearDown(self):
self.tmp.cleanup()
def test_normal_name_is_allowed(self):
self.assertTrue(str(safe_path(self.root, "my-app/status.json")).startswith(self.root))
def test_traversal_absolute_and_symlink_are_rejected(self):
for bad in ("../../etc/passwd", "/etc/passwd", "link/passwd", "a/../../x.json"):
with self.assertRaises(ToolInputError, msg=bad):
safe_path(self.root, bad)
class CheckUrlTests(unittest.TestCase):
def test_private_and_mapped_addresses_are_rejected(self):
for bad in (
"https://127.0.0.1/",
"https://[::ffff:127.0.0.1]/",
"https://[::ffff:7f00:1]/",
"https://[0:0:0:0:0:FFFF:A9FE:A9FE]/", # 169.254.169.254, cloud metadata
"https://10.0.0.5/admin",
"https://localhost/",
"http://8.8.8.8/", # plain http is not on the scheme allowlist
"https://8.8.8.8/api/..;/admin",
):
with self.assertRaises(ToolInputError, msg=bad):
check_url(bad)
def test_public_ip_literal_is_allowed(self):
self.assertEqual(check_url("https://8.8.8.8/dns-query"), ["8.8.8.8"])
if __name__ == "__main__":
unittest.main(verbosity=2)How to check if your MCP server is affected
Start with the packages you actually ship:
# Node: official SDK packages (need sdk >= 1.32.0, server/client >= 2.3.0, express >= 2.0.2)
npm ls @modelcontextprotocol/sdk @modelcontextprotocol/server @modelcontextprotocol/client @modelcontextprotocol/express
# Python: vendor servers and frameworks from the table
pip show awslabs.aws-serverless-mcp-server fast-agent-mcp mcp-contextforge-gateway chainlit agentscope 2>/dev/null | grep -E "^(Name|Version)"
# Go: mcp-go needs v1.2.1 or later
go list -m github.com/mark3labs/mcp-goThen search your own code for the patterns, not just the packages. Look for a transport or McpServer created at module level, tool handlers that query by ID without an owner filter, and open(), fetch() or subprocess calls fed directly from tool arguments. If you moved to the newer spec, my MCP 2026-07-28 stateless migration guide covers building a server per request, which also avoids the shared transport bug.
MCP security hardening checklist
- Upgrade the official TypeScript SDK to 1.32.0 (1.x) or 2.3.0 or later (2.x), then enable the audience check and the input element bound.
- Build one
McpServerand transport per request, or per session with a session ID generator. Never share one across clients. - Bind each session to the authenticated user ID, and re-check the token on every request, not only at
initialize. - Validate the token audience on the server. On the client, only accept a Protected Resource Metadata
resourcethat matches the server URL you configured. - Don't follow cross-origin redirects with custom headers, session IDs or OAuth bodies attached.
- Call the same authorization function in each MCP tool that your REST route calls. Add a test with a low-privilege user for every tool.
- Resolve file paths and check containment after resolving. Keep write flags such as
--enable-writeoff unless you need them. - Classify outbound addresses numerically, unwrap IPv4-mapped IPv6, enforce at connect time, and recheck redirects.
- Never build shell commands from tool input. If a tool must start processes, use a fixed allowlist of executables.
- Bind HTTP mode to
127.0.0.1by default, require auth, and checkHostandOriginheaders. - Treat tool names and descriptions from third-party servers as untrusted input; CVE-2026-54140 in OpenHuman was a poisoned tool description.
- Watch advisories for every MCP package you run. The same week also brought Pydantic AI 2.66 with 10 advisories.
Is MCP secure?
The protocol gives you the pieces: OAuth with resource indicators, session IDs, and per-tool schemas. Almost every bug in this batch came from implementations that used those pieces loosely or added MCP beside an existing app without carrying its checks over. My view: treat an MCP server like any public API that a semi-trusted user drives. If you'd add an ownership check, a rate limit or an egress rule to a REST endpoint, the MCP tool needs it too.
Frequently asked questions
What is an MCP vulnerability?
It is a security bug in software that speaks the Model Context Protocol, such as an MCP server, client, SDK or gateway. In November 2026 most of them were implementation bugs like path traversal, missing authorization in tools, SSRF, OAuth token confusion and session hijacking, not flaws in the protocol spec itself.
Is MCP secure?
The protocol includes OAuth, resource indicators and session IDs, but security depends on how each server uses them. Treat every MCP tool like a public API endpoint: authenticate the caller, check permissions per object, confine file paths and restrict outbound network access.
How do I check if my MCP server is affected?
List the MCP packages you ship with npm ls, pip show or go list -m and compare them with the fixed versions in the advisories. Then search your code for a server or transport created once at startup, tools that read by ID without an owner check, and file, fetch or subprocess calls built from tool arguments.
Was there an Anthropic MCP vulnerability in November 2026?
The official MCP TypeScript SDK had several CVEs whose records reached NVD on 5 November 2026, including CVE-2026-111770 for shared transports leaking replies between clients. They are fixed in @modelcontextprotocol/sdk 1.32.0 and the 2.3.0 server and client packages, but some fixes also need code or configuration changes.
Which MCP vulnerabilities from November 2026 have no fix yet?
As of 10 November 2026, AgentScope CVE-2026-123022 (command injection, up to 2.0.11) and Chainlit CVE-2026-120492 (SSRF allowlist bypass in 2.12.0) had no fixed release. Disable the affected MCP features until patches ship.
Sources
- GHSA-jm63-cq47-j5jp: Shared transport or server instances delivered one HTTP client's messages to another (CVE-2026-111770) (MCP TypeScript SDK on GitHub, 2026-10-15)
- GHSA-rvq5-wwqv-78pq: Server bearer authentication accepted tokens issued for another service (CVE-2026-111771) (MCP TypeScript SDK on GitHub, 2026-10-05)
- GHSA-6prh-2h8m-c8cw: Cross-origin redirects re-sent headers and request bodies (CVE-2026-111769) (MCP TypeScript SDK on GitHub, 2026-10-02)
- GHSA-46jr-4wx8-v453: One HTTP request could exhaust server memory during validation (CVE-2026-111768) (MCP TypeScript SDK on GitHub, 2026-10-15)
- AWS security bulletin 2026-164-AWS: CVE-2026-116635 in the AWS Serverless MCP Server (AWS, 2026-11-02)
- GHSA-r6mm-377j-qg8m: Path traversal in the AWS Serverless MCP Server (awslabs/mcp on GitHub, 2026-11-02)
- GHSA-pgh9-49q4-hqcx: SurrealDB MCP session hijack via BoundSubject fingerprint collision (CVE-2026-121419) (SurrealDB on GitHub, 2026-10-19)
- GHSA-xf6j-693p-wqxx: mcp-go OAuth client accepts an unvalidated resource (CVE-2026-123272) (mark3labs/mcp-go on GitHub, 2026-10-09)
- GHSA-95vg-3r9c-2v5q: ChatCLI MCP OAuth resource indicator (CVE-2026-123271) (ChatCLI on GitHub, 2026-10-11)
- GHSA-x5cc-8486-8qqx: Authorization bypass in Instatic AI/MCP content read tools (CVE-2026-121458) (Instatic on GitHub, 2026-11-03)
- GHSA-mj45-84p2-v77q: Trawl MCP public-destination bypass via IPv4-mapped IPv6 (CVE-2026-122140) (Trawl on GitHub, 2026-11-03)
- CVE-2026-120492: Chainlit 2.12.0 SSRF via semicolon path segment in MCP allowed_urls (NVD, 2026-11-02)
- CVE-2026-123022: AgentScope OS command injection in the MCP Workspace API (NVD, 2026-11-05)
- CVE-2026-120450: LaraDashboard improper authorization via the MCP send-email tool (NVD, 2026-11-02)
- Security bulletin: IBM ContextForge MCP Gateway denial of service (CVE-2026-18484) (IBM, 2026-10-27)
- GHSA-p7w7-4929-vpj5: Unauthenticated HTTP MCP tool invocation in the Dynatrace MCP server (CVE-2026-123811) (Dynatrace on GitHub)
- GHSA-9vhv-g5hf-m7m7: fast-agent serve binds to 0.0.0.0 with no authentication (CVE-2026-102610) (fast-agent on GitHub)
- GHSA-5m6c-38r8-5w76: Dagu MCP dagu_read path traversal (CVE-2026-102943) (Dagu on GitHub)
- CVE-2026-54140: OpenHuman MCP tool description injection (NVD, 2026-11-05)
- AgentScope pull request 3448: opt-in allowlist for stdio MCP commands (AgentScope on GitHub)
Keep reading
- FastMCP Vulnerability CVE-2026-120338: Check and Fix Prefab UIA CVSS 10.0 FastMCP vulnerability in prefab-ui lets Generative UI code read and write host files. Check your versions, upgrade and look for planted .pth files.
- MCP Stateless Update: Migrate Your Server to 2026-07-28MCP's 2026-07-28 spec drops sessions and the initialize handshake. What breaks in your MCP server, and how to migrate a TypeScript server step by step.
- Pydantic AI 2.66 Security Upgrade: Patch 10 AdvisoriesPydantic AI 2.66.0 patches 10 advisories (2 High). Upgrade paths for 2.x, 1.x and harness, plus rotate guidance.
Comments
Share a question or note about this article.
Loading comments…