GHSA-8Q49-2H5H-434X: Poller SSRF Guard Restored in FrontMCP
Summary
FrontMCP's OpenAPI spec poller previously bypassed the SSRF protections used during the initial spec load by calling the global fetch() directly. The patch routes poll-time retrieval through mcp-from-openapi's safeFetch(), propagates normalized SSRF policy and redirect behavior from adapter configuration, and adds end-to-end tests covering default loopback blocking, explicit internal-IP opt-in, and adapter-level error surfacing. Based on the provided diff, this is a root-cause fix for the poller-specific SSRF gap.
Analysis
Vulnerability
GHSA-8Q49-2H5H-434X describes a server-side request forgery condition in the FrontMCP OpenAPI spec poller. The vulnerable path was not the initial OpenAPI load, but the background re-fetch logic used for polling. The initial load already relied on SSRF-aware logic in mcp-from-openapi, while the poller performed a direct global fetch(this.url, ...) with only timeout handling. That created a policy gap: an attacker-influenceable spec URL could be re-polled without resolved-IP validation, connection pinning, or redirect re-validation.
The diff shows the vulnerable implementation in libs/adapters/src/openapi/openapi-spec-poller.ts using raw fetch() against this.url. Because polling re-fetches the same URL on a timer, this bypass enabled SSRF against internal interfaces, including scenarios involving DNS rebinding or redirect chains, exactly as summarized in the advisory context. The issue is therefore architectural inconsistency: the same trust boundary was enforced during initial load but not during subsequent poll cycles.
// vulnerable poll path
const response = await fetch(this.url, {
headers,
signal: controller.signal,
});Relevant sources: pull request #510, fix commit 077201e, and the GitHub Security Advisory.
Patch
The patch replaces the raw network primitive in the poller with the same guarded fetch path used by the initial OpenAPI load. In libs/adapters/src/openapi/openapi-spec-poller.ts, the code now imports normalizeSsrfOptions, safeFetch, and ResolvedSsrfOptions from mcp-from-openapi. The poller stores normalized SSRF settings and a redirect policy, defaulting redirects to false, then calls safeFetch() with timeoutMs, followRedirects, and ssrf.
const response = await safeFetch(this.url, {
headers,
timeoutMs: this.fetchTimeoutMs,
followRedirects: this.followRedirects,
ssrf: this.ssrf,
});The adapter wiring is also important. In libs/adapters/src/openapi/openapi.adapter.ts, polling options are rebuilt so the poller inherits the same SSRF policy derived from loadOptions.refResolution via normalizeSsrfOptions(this.resolveRefResolution()). Redirect behavior is likewise propagated from this.options.loadOptions?.followRedirects ?? false. This closes the configuration drift between initial load and poll-time refresh.
The patch extends the poller types to explicitly carry ssrf?: ResolvedSsrfOptions and followRedirects?: boolean, documenting that polling defaults to blocking internal/private addresses and that redirect hops are re-validated when enabled. The test suite is strengthened in libs/adapters/src/openapi/__tests__/openapi-polling-e2e.spec.ts by preserving the real SSRF guard instead of fully mocking mcp-from-openapi. New tests verify that loopback URLs fail closed by default, internal polling works only with explicit allowInternalIPs opt-in, and adapter-level polling surfaces SSRF blocks without emitting updates. The docs were updated consistently in the adapter and polling guides to explain the inherited SSRF policy and the trusted-only nature of allowInternalIPs.
Review
Pros
- The patch addresses the actual trust-boundary mismatch by removing the raw
fetch()from the poller and reusing the hardenedsafeFetch()path already established for initial spec loading, as shown in the fix commit. - SSRF policy is propagated from adapter configuration into polling via
normalizeSsrfOptions(this.resolveRefResolution()), which reduces the chance of future divergence between initial load and refresh behavior. - Redirect handling is explicitly controlled and defaults to
false, with documentation stating that redirect hops are re-validated. That is directly relevant to the advisory's redirect-based exploitation angle. - The tests are materially improved: they stop over-mocking the SSRF layer and add negative and positive coverage for loopback blocking, explicit internal-IP opt-in, and adapter-level logging/error behavior. This is stronger than a unit-only assertion because it exercises the real guarded fetch path.
- Documentation updates are aligned with the code changes and clearly state fail-closed behavior, default blocking of loopback/private/internal targets, and the risk of enabling
allowInternalIPsfor untrusted URLs.
Cons
- The reviewable evidence is limited to the provided diff and snippets; the security properties of the fix still depend on the correctness of
mcp-from-openapi'ssafeFetch()andnormalizeSsrfOptions(), which are referenced but not expanded in the supplied material. - The patch appears scoped to the OpenAPI spec poller path only. That is appropriate for this advisory, but it does not by itself prove that no other background refresh or secondary fetch paths in the codebase still use raw network primitives.
- The new standalone poller option
ssrf?: ResolvedSsrfOptionsis powerful; if downstream callers construct permissive policies directly, they can still opt themselves into risky behavior. The docs warn about this, but the API remains security-sensitive.
Verdict
Root-cause.
The vulnerable behavior existed because the poller used a separate, unguarded network path for the same attacker-influenceable URL that was already protected during initial load. The patch removes that bypass by routing poll-time fetches through the same SSRF enforcement mechanism, carrying over normalized SSRF policy and redirect settings from the adapter. The added tests specifically validate the previously missing security invariant: polling must fail closed on internal targets unless explicitly opted in. Based on the supplied sources, this is not merely input filtering or logging around the old behavior; it is a direct replacement of the unsafe primitive with the intended guarded primitive.
Sources: https://github.com/agentfront/frontmcp/pull/510, https://github.com/agentfront/frontmcp/commit/077201e109bf6f45dbc85c36d6bd77ded18ab13e, https://github.com/advisories/GHSA-8Q49-2H5H-434X, https://cvereports.com/reports/GHSA-8Q49-2H5H-434X.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- SSRF.api
Best direct match for this GHSA because the issue involves an OpenAPI spec poller making unsafe outbound requests. This hands-on lab focuses on server-side request forgery in an API/OpenAPI context and is aligned to CWE-918 and OWASP A10:2021, making it highly relevant for learning defensive controls such as URL validation, redirect handling, and internal network access restrictions.
- SSRF.ts
Strong fit if you want to practice defensive fixes in a JavaScript/TypeScript ecosystem similar to a fetch()-driven poller. Useful for understanding how unsafe server-side fetch logic can be abused through attacker-controlled URLs, and how to harden request code with allowlists, DNS/IP checks, and redirect protections.
- Capital0.ts
Recommended as the deeper follow-up lab. It covers a harder SSRF scenario and is useful after the introductory lab because the GHSA mentions bypassing initial protections via DNS rebinding or redirects. This makes it a good defensive progression for practicing layered mitigations rather than a single check.