Buffer First, Validate Later: mcp-go Reads Every POST Body Into Memory With No Fix Available

NVD published CVE-2026-108859 at 14:17Z on 11 October 2026 against mark3labs/mcp-go — the Go implementation of the Model Context Protocol, with 9,157 stars and 902 forks — carrying 8.7 high on CVSS 4.0 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N), CWE-770, affecting every release through 1.2.1, with no fixed version named. The record’s vulnStatus is Deferred, with the VulDB-submitted CVSS carried as primary — no independent NVD analysis is coming. The defect is one line: the Streamable HTTP transport reads the complete body of every POST request into a byte slice before it validates anything about that request.

The report, credited to hieuPenguinnn — the same researcher behind the hyper-mcp cosign bypasses covered here last week — is unusually careful about what it does and does not claim, and the carefulness is the reason to take it seriously. It is explicitly static-only: source-reviewed at release tag v1.2.1 and at the then-current main, with no runtime validation performed. It states impact by scenario rather than asserting universal remote exposure, because reachability depends on the embedding application exposing the handler without an upstream body limit. That restraint makes the core finding easy to verify, so we verified it.

What the handler does, in order

We read server/streamable_http.go at main HEAD today (the repository was last pushed 9 October). ServeHTTP is declared at line 388. Inside the POST branch, at line 410:

body, bodyErr = io.ReadAll(r.Body)

The content-type check and the JSON parse run later, inside handlePost. Go’s net/http applies no default limit to request-body size, and this handler adds none: the strings MaxBytesReader and LimitReader do not appear anywhere in the file, at either the tagged release or HEAD. The SDK’s own convenience listener confirms the posture is structural rather than incidental. Start(addr) constructs its http.Server with only Addr and Handler set — no read timeout, no MaxHeaderBytes, no body cap. There is no ReadTimeout anywhere in the file. The single guard present on the path, a Host-header DNS-rebinding check, does not cap body bytes and does not authenticate the caller: this path takes no authentication at all.

The trust boundary being crossed is worth stating plainly because it recurs across this ecosystem: untrusted request bytes are buffered in process memory before any protocol or content-type validation. A peer that can send POST requests to the endpoint can make the process allocate memory proportional to the submitted body, and can repeat that with concurrent requests until the server degrades or the OOM killer ends the discussion. Garbage that is not even JSON gets fully buffered first; the decision that it is garbage happens afterwards, on the copy already in memory.

The same line, in three ecosystems

The report notes — as the researcher’s stated analysis, which we did not independently verify — that the identical unbounded-body class has already drawn CVE-2026-67432 (CVSS 7.5) in the Ruby MCP SDK and CVE-2026-14539 in a Go MCP server built on the same io.ReadAll pattern. Whether or not those two records describe exactly this shape, the pattern is now visible often enough to name: MCP’s Streamable HTTP transport arrived with session management, rebinding guards and CORS handling, and without the resource controls that every other exposed HTTP endpoint learned to carry a decade ago. Each SDK is re-learning that io.ReadAll(r.Body) on an unauthenticated handler is a memory-allocation oracle for anyone who can reach the port.

That is also why the CVSS 4.0 score of 8.7 for a pure availability finding deserves a moment rather than a shrug. Availability-only 8.x scores usually signal “one packet, total outage.” Here the mechanism earns it honestly: no privileges, no user interaction, network-reachable, and concurrency multiplies a single oversized body into process death. The score’s honesty cuts both ways, though. The AT:N and the absence of any safety or automaton-impact modifiers treat this as a classical resource-exhaustion bug that happens to sit in AI infrastructure. There is nothing AI-specific about the defect. Everything about its blast radius is AI-specific: this SDK is what a large share of Go-based MCP servers are built on, and MCP servers are what agents connect to.

What to do, with versions that actually exist

There is no upgrade that fixes this. The latest tag is v1.2.1 and it is affected; main HEAD still contains the exact line. We found no upstream issue or fix pull request for it. Until one lands, the controls are all outside the library:

  • Cap the body before it reaches the handler. Terminate MCP HTTP behind a reverse proxy with an explicit body limit (client_max_body_size, LimitRequestBody, or equivalent), or wrap the handler in http.MaxBytesHandler in your own http.Server — do not rely on the SDK’s Start(), which sets no timeouts and no limits.
  • Do not expose the endpoint bare. Bind to localhost where the deployment allows it, require authentication at the proxy, and treat the Streamable HTTP port as an unauthenticated allocation primitive — because until a body cap exists, that is what it is.
  • Watch memory, not just errors. Concurrent oversized POSTs degrade before they kill; per-process RSS alerting on MCP server processes catches the scenario the score describes.

The broader lesson is a one-line code-review rule for every MCP transport implementation in every language: find the first read of the request body and check that a limit is applied before it, not validated after it. The Ruby SDK, a Go server, and now the Go SDK all failed that check in the same way. There will be a fourth.

Verification note. The CVE identifier, publication timestamp (14:17:05Z, 11 October 2026), CVSS 3.1 and 4.0 scores and vectors, CWE-770 assignment, affected-through version and vulnStatus: Deferred were read first-hand from the NVD 2.0 API. The VulnCheck advisory text, date and researcher credit were read first-hand from vulncheck.com. The root-cause description, line numbers at the two reviewed revisions, and the statements about absent MaxBytesReader/LimitReader, the Start() server construction, the rebinding guard, and the scenario-scoped impact language are the researcher’s published analysis, read first-hand from the linked HackMD write-up and attributed as such. Independently, we read server/streamable_http.go at main HEAD via the GitHub API: ServeHTTP at line 388, io.ReadAll(r.Body) at line 410, no MaxBytesReader or ReadTimeout in the file, and a Start() that sets only address and handler. Star, fork, push-date and tag data were read first-hand from the GitHub API; the sibling-CVE claim (CVE-2026-67432, CVE-2026-14539) is reported from the write-up only. We did not install the SDK, send any oversized request, or test any deployment. The remediation guidance is our editorial analysis. We have no evidence of exploitation and imply none.

Sources: