A AegiFlow
MEDIUMCVSS 5.9

GHSA-8q49-2h5h-434x

FrontMCP: Server-Side Request Forgery (SSRF) in the OpenAPI adapter spec-change poller

Published
2026-07-24
Modified
2026-07-24
Sources
github-advisory

Summary

## Summary The OpenAPI adapter's spec-change **poller** (`OpenApiSpecPoller`) re-fetched the configured spec `url` on a timer using a raw global `fetch()`, bypassing the SSRF guard (`safeFetch` / `assertUrlSafe`) that `OpenAPIToolGenerator.fromURL()` applies to the initial spec load. As a result, the pinning/DNS-resolution hardening delivered via `mcp-from-openapi >= 2.5.0` (advisory GHSA-65h7-9wrw-629c) protected the initial load but **not** the recurring poll of the same URL. When polling is enabled against an untrusted or attacker-influenceable spec URL, this is an unguarded SSRF vector. ## Details The initial spec load is guarded. `OpenapiAdapter` resolves a secure `refResolution` policy and passes it to the guarded loader: ```ts // libs/adapters/src/openapi/openapi.adapter.ts — initializeGenerator() return await OpenAPIToolGenerator.fromURL(this.options.url, { // ... followRedirects: this.options.loadOptions?.followRedirects ?? false, refResolution, // secure default: external $refs off, internal targets blocked }); ``` But the poller — which re-fetches **the same URL** on every interval — did not: ```ts // libs/adapters/src/openapi/openapi-spec-poller.ts — doFetch() (vulnerable, controller.abort(), this.fetchTimeoutMs); try { const response = await fetch(this.url, { // <-- raw global fetch, no SSRF guard headers, signal: controller.signal, }); // ...hash the body, fire onChanged... } ``` Because `doFetch()` never called `safeFetch`, none of the guard's protections applied to the polled request: - no allow-list / block-list enforcement (`allowedHosts` / `blockedHosts`); - no internal/private/loopback/link-local/CGNAT/cloud-metadata IP blocking; - no DNS resolution of the hostname (so a DNS name that resolves to an internal IP, e.g. `http://127.0.0.1.nip.io/`, was reached); - no connection **pinning** to the validated IP (DNS-rebinding TOCTOU); - no per-hop re-validation of HTTP redirects. This is the identical threat model to `fromURL()` / external `$ref` resolution (GHSA-65h7-9wrw-629c), applied to a request path that the fix for that advisory did not cover. ## Impact A server that enables spec polling against an untrusted or attacker-influenceable spec URL will, on every poll interval, issue a server-side `GET` to whatever host the URL (or a DNS name it resolves to, or a redirect it returns) points at — including internal-only addresses unreachable from the public internet. Consequences include: - reading cloud-instance metadata endpoints (e.g. `169.254.169.254`) — credential / token theft; - probing and reaching internal services and private-range hosts (internal network scanning); - DNS-rebinding to swap a public host for an internal one between validation and connection. The poller issues `GET` requests only, so the primary impact is **confidentiality** (reaching and reading internal endpoints); the fetched body is content-hashed to detect change and the subsequent tool rebuild goes back through the guarded `fromURL()` path. ## Preconditions Exploitation requires **both**: 1. `polling.enabled: true` on an `OpenapiAdapter` (polling is off by default and requires the URL-based `url` option, not an inline `spec`); **and** 2. the spec `url` is untrusted / attacker-influenceable (e.g. it is derived from user input, a tenant-supplied value, or otherwise not a fixed trusted constant), or an otherwise-trusted spec host is attacker-controlled or can redirect. Servers that poll a fixed, trusted, first-party spec URL are not exposed in practice, though they still benefit from the guard as defense-in-depth. ## Proof of concept ```ts import { OpenapiAdapter } from '@frontmcp/adapters'; // url is attacker-influenceable and points (directly, via DNS, or via redirect) // at an internal target; polling re-fetches it every interval. const adapter = OpenapiAdapter.init({ name: 'evil', url:

Affected packages

EcosystemPackageAffected versionsFixed versions
npm@frontmcp/adapters1.5.6

Remediation: Upgrade to 1.5.6 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.