Five mcp-remote CVEs Say “Arbitrary Code Execution.” The Research They Cite Does Not.

On September 24, MITRE published five CVE records against mcp-remote — the stdio-to-HTTP bridge that lets MCP clients which only speak local stdio connect to remote MCP servers. The package pulled 784,494 downloads in the week ending September 21. Its GitHub repository carries 1,603 stars. If you run Claude Desktop, Cursor, or any other stdio-only MCP client against a hosted server, there is a good chance this binary is in your process tree.

The five records are CVE-2026-51994, CVE-2026-51995, CVE-2026-51996, CVE-2026-51997, and CVE-2026-52001. All five were published within four-tenths of a second of each other at 15:17 UTC. All five cite the same source: a public research package by the researcher playb0t, tagged v1.0.1, containing seven bounded advisory records.

Read the CVE descriptions and then read the advisories they point at, and three separate problems fall out. This is not a story about a new attack on MCP infrastructure. It is a story about what happens to a careful piece of research on its way through the CVE pipeline — and it has a concrete consequence for anyone whose exposure decisions are driven by a scanner.

Problem one: two records describe findings the researcher retracted

CVE-2026-51996 reads: “An issue in geelen mcp-remote 0.1.16 through 0.1.38 allows a remote attacker to execute arbitrary code via the src/lib/utils.ts and the getServerUrlHash function.” Its sole technical reference is advisories/F-04-md5-token-isolation.md.

That file is titled “MD5-based storage namespace hardening.” Its classification block reads:

- CWE-328: Use of Weak Hash
- Evidence: defense-in-depth / corrected claim
- CVSS: not applicable to the current evidence
- CVE: no public record claimed

The finding is that getServerUrlHash() uses MD5 to derive per-server directory names under ~/.mcp-auth/. The advisory’s own “Correction and limitations” section says the original write-up “overstated the practical mechanism,” explains that a chosen-prefix collision is not the same thing as a second-preimage attack against an already-fixed identifier, and states plainly: “No token namespace takeover or real-token access was demonstrated.” The project’s CORRECTIONS.md repeats it.

A weak hash used as a filename encoding became, in the CVE record, remote arbitrary code execution.

CVE-2026-52001 has the same shape. It cites F-11-sse-token-origin-scope.md, which is also marked “defense-in-depth / corrected claim,” and which spends most of its length explaining why the original claim does not hold: the pinned @modelcontextprotocol/sdk@1.25.3 already compares the SSE endpoint origin against the connection origin, and the pinned undici@7.12.0 already strips authorization and cookie headers on cross-origin redirects. The advisory’s words: these controls “refute the original claim of a current token-forwarding exploit path.” The CVE records it as information disclosure.

The researcher put a bold note at the top of the advisory index — “F-04 and F-11 do not claim current CVE eligibility” — and a second one below it: “No new CVE identifier is claimed until a public CVE record binds it to the corresponding advisory.” Both got CVEs anyway.

Problem two: “arbitrary code execution” appears twice with no supporting evidence class

CVE-2026-51997 also asserts remote code execution, “via the open() functions.” Its reference is F-08-browser-url-validation.md, whose actual claim is that the URL check added in 0.1.16 — the fix for CVE-2025-6514, the 9.6-rated command injection JFrog disclosed in July 2025 — accepts HTTP(S) URLs but does not reject loopback, private, link-local, or cloud-metadata destinations before handing the URL to the user’s browser. Classification: CWE-20, evidence class “source review,” impact described as internal-service interaction, reconnaissance, or browser-mediated CSRF. Not code execution. The advisory explicitly notes that “browser security controls and the behavior of the destination service affect practical impact.”

The distinction matters because open() is exactly where the 2025 command-injection bug lived. A description that says “arbitrary code execution via the open() functions” reads, to anyone triaging quickly, as a regression of CVE-2025-6514. It is not one. The current release wraps the URL in sanitizeUrl() from strict-url-sanitise before calling open — which is the 2025 fix, still in place.

Of the five, only two survive contact with their sources intact. CVE-2026-51994 (SSRF via the resource_metadata URL lifted from a server’s WWW-Authenticate header) and CVE-2026-51995 (blind SSRF via authorization_servers[]) both map to findings the researcher marked “Local PoC, reverified” — validated with localhost-only canaries in February and re-confirmed in May. Those are real, and they are the ones worth your attention. CISA’s ADP enrichment attached a CVSS 3.1 base of 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) to CVE-2026-51995 on the day of publication; the researcher declined to assert any numeric score, writing that “the demonstrated evidence does not independently establish every impact metric.”

Worth noting what even the strong findings do and don’t show: the F-01 advisory states the localhost canary “demonstrated request issuance; it did not demonstrate response exfiltration.” That is an honest scoping statement, and it is the kind of detail that disappears entirely when a finding is compressed into one sentence.

Problem three: the version ranges point at a package that no longer exists

This is the part with operational consequences.

Every one of the five CVEs pins its upper bound at 0.1.38. That was correct at disclosure — the researcher’s TIMELINE.md records 0.1.38 as the current release on 2026-07-31 and notes “Later upstream release: none known as of disclosure.” The package had sat untouched since February 5, 2026.

Then, three weeks after disclosure, it moved. npm’s registry metadata shows 0.1.39 through 0.1.43 published on August 21, another six releases on August 23, 0.2.0 on August 24, and a further march up to 0.14.3 on September 21 — 51 releases in a month against a package that had shipped nothing in six. The maintainer list changed with it: 0.1.38 was published by threepointone and geelen; punkpeye appears from 0.1.39 onward and is now the sole maintainer. The repository moved too — github.com/geelen/mcp-remote now issues a 301 to github.com/punkpeye/mcp-remote.

None of that is visible in the CVE records. A defender reading “0.1.16 through 0.1.38” has no way to learn from NVD whether 0.14.3 is fixed, partially fixed, or unfixed. There is no “fixed in” version, because at the time the CVEs were minted there was no maintainer response to point at.

We checked the published 0.14.3 tarball directly. The picture is mixed, and it is worth being precise about it:

  • F-04 (MD5 namespace) is unchanged. getServerUrlHash() still ends in crypto.createHash("md5"). It now folds more inputs into the digest — authorize resource, sorted authorize params, sorted headers, client metadata URL, token endpoint — which makes the namespace more specific, but the primitive is the same. Given the researcher withdrew the exploitability claim, this is a hygiene item, not an exposure.
  • F-09 (cleartext token storage) is unchanged in kind. Tokens still land as files under ~/.mcp-auth/ (or MCP_REMOTE_CONFIG_DIR), written with mode: 384 — that is 0o600, the restrictive permission the advisory credits to 0.1.37. The writes are now atomic via temp-file-and-rename. Still cleartext on disk; still not in an OS keychain.
  • F-08 (browser launch) retains the 2025 sanitizer, as described above.
  • The discovery fetch path still has no destination policy we could find. discoverOAuthProtectedResourceMetadata() passes the server-supplied resourceMetadataUrl through to a fetch with no scheme, hostname, or private-address classification anywhere in the call chain. Searching the bundle for loopback and link-local guards turns up checks in three unrelated places — a TLS requirement on the token endpoint, the local callback server binding, and a CLI validation of --token-endpoint — but nothing gating outbound discovery requests. There is one redirect: "manual" in the bundle, and it is in the cached dynamic-client-registration preflight, not in the metadata discovery path that F-10 describes.

That is a read of a minified bundle, not a reproduction, and we are labelling it as such: it establishes that the specific guards the advisories ask for are absent from the shipping code, not that the SSRF is exploitable in 0.14.3. But it means the two findings with real PoC evidence — the ones that did earn their CVEs — have no visible remediation in the current release, while the CVE records tell scanners the affected range topped out seven months and 51 versions ago.

The feed problem underneath all of this

Query these five CVE IDs against OSV and all five come back. Query GitHub’s global advisory database for npm advisories affecting mcp-remote and you get exactly one result: GHSA-6xpm-ggf7-wc3p, the 2025 command-injection record. The September 2026 batch is not there.

OSV’s own ingestion illustrates the gap. Because MITRE’s records carry "product": "n/a" and "version": "n/a" in their structured affected-products fields, OSV had to parse the version range out of the free-text description — its metadata literally records "source": "DESCRIPTION" for the extracted introduced and fixed events. It resolved them to GIT commit ranges on github.com/punkpeye/mcp-remote, and mapped fixed to 02619aff36e79803d7c894e8c8ae7b34b2d11f8c — which is the commit the researcher identifies as the reviewed vulnerable release, 0.1.38. An advisory whose upper bound is a version gets reinterpreted downstream as a version where the bug is fixed.

We flagged the mirror image of this a day ago with the DBHub advisories, where a real CVE was invisible to NVD and the GHSA database entirely. The failure modes are opposite and the lesson is the same: for AI and agent tooling, the CVE feeds are currently a lagging, lossy index rather than a reliable inventory. The LiteLLM KEV entry showed what it looks like when the pipeline works. This shows what it looks like when it does not.

Why this keeps happening to MCP packages specifically

Three structural conditions line up here, and they are not unique to mcp-remote.

The first is that OAuth discovery in an MCP bridge is a genuine trust boundary that does not look like one. The researcher’s framing is the sharpest line in the whole package: “Remote metadata is not passive data. Every URL, redirect, origin, and credential handoff is a trust decision.” A bridge that fetches a URL because a server told it to in a WWW-Authenticate header is making an outbound request on behalf of an untrusted party, from inside the user’s network. That is the same shape as the SSRF class we covered in the GitLab MCP advisories — and it keeps recurring because the protocol’s discovery flow hands URL selection to the remote side by design.

The second is maintainer discontinuity. The disclosure timeline records a private report on 2026-02-17, revalidation on 2026-05-03 after no acknowledgement, the 90-day window elapsing on 2026-05-18, and public disclosure on 2026-07-31 — five and a half months with no maintainer response. Then the package changed hands and was rewritten at high velocity. An unresponsive maintainer and a fast-moving new one produce the same outcome for a CVE record: version data that is stale on arrival.

The third is that careful evidence grading does not survive the trip. This research package is unusually disciplined — it separates “Local PoC, reverified” from “Source review” from “defense-in-depth / corrected,” it publishes a corrections file that admits three of its own CVSS vectors did not match their displayed scores, and it declines to claim CVEs for findings it downgraded. Every one of those distinctions was flattened into a single sentence per CVE, and in two cases the flattening inverted the researcher’s conclusion.

What to do

  • Do not treat these five CVEs as five vulnerabilities. Two (CVE-2026-51994, CVE-2026-51995) rest on reverified local PoCs. One (CVE-2026-51997) is a source-review finding whose description overstates impact. Two (CVE-2026-51996, CVE-2026-52001) trace to findings the researcher explicitly withdrew.
  • Do not read “0.1.38” as “fixed in 0.1.39.” The upper bound is the last version the researcher reviewed, not a remediation boundary. The maintainer and repository both changed after disclosure; treat the current line as unaudited against these findings rather than as patched.
  • Constrain the bridge at the network layer. The two credible findings are outbound SSRF from your host. Egress policy on the process running mcp-remote — deny loopback beyond its own callback port, deny RFC1918, deny 169.254.169.254 — neutralises them regardless of which version you run. That control does not depend on the CVE record being right.
  • Treat ~/.mcp-auth/ as a credential store. Access tokens, refresh tokens, client secrets, and PKCE verifiers sit there in cleartext with 0600 permissions. That stops other users; it does not stop same-user malware, backup tooling, or an agent with filesystem access. Exclude it from sync and backup scopes, and scope the OAuth clients it holds narrowly.
  • Check whether your scanner returns anything for these IDs. GitHub’s npm advisory database does not currently carry them. If your dependency alerting is GHSA-sourced, this batch is invisible to it — and so is the fact that the package changed owners.
  • When a CVE description and its cited advisory disagree, believe the advisory. This batch has a linked primary source for every record. Not every batch does, which is the more common and worse failure.

There is a version of this story where the headline is “five new CVEs in a package with 784,000 weekly downloads,” and that version would be wrong in a way that costs defenders time. The useful finding is narrower and more uncomfortable: a researcher did the hard part correctly — bounded the claims, graded the evidence, published the corrections, declined the CVEs they did not think were earned — and the identifier system that sits downstream discarded exactly the distinctions that made the work trustworthy. Agent tooling is being shipped and rewritten faster than the vulnerability-tracking infrastructure around it can describe, and when the two disagree, the raw advisory is the artifact with the evidence in it.

Sources: