GHSA-8q49-2h5h-434x: FrontMCP Server-Side Request Forgery (SSRF) Vulnerability
MODERATEGHSA-8q49-2h5h-434x is a MODERATE (CVSS 5.9) server-side request forgery (ssrf) vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting FrontMCP libraries in the npm ecosystem. The issue involves server-side request forgery (ssrf) in the openapi adapter spec-change poller. Remediation is available by upgrading affected packages to version 1.5.6.
GHSA-8q49-2h5h-434x at a Glance
Security Impact
CVSS: 5.9
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N).
How to Fix GHSA-8q49-2h5h-434x
To remediate this vulnerability, upgrade all affected FrontMCP dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install @frontmcp/adapters@1.5.6
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls @frontmcp/adapters
How to Check if You Are Affected
To determine whether your Node.js application is affected by GHSA-8q49-2h5h-434x, inspect your project dependencies using npm:
npm ls @frontmcp/adapters
Analyze the terminal output to locate any version of FrontMCP in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.5.6), your application is affected.
Note: The npm ls @frontmcp/adapters command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In FrontMCP, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-8q49-2h5h-434x Affected FrontMCP Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
@frontmcp/adapters
|
npm |
< 1.5.6
|
1.5.6
|
References
Frequently Asked Questions
What is GHSA-8q49-2h5h-434x?
Which FrontMCP versions are affected?
What version fixes GHSA-8q49-2h5h-434x?
Is @frontmcp/adapters affected?
What is the CVSS score of GHSA-8q49-2h5h-434x?
What CWE is associated with GHSA-8q49-2h5h-434x?
How can I check npm dependencies for GHSA-8q49-2h5h-434x?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-8q49-2h5h-434x",
"modified" : "2026-07-24T22:40:00Z",
"published" : "2026-07-24T22:40:00Z",
"aliases" : [ ],
"summary" : "FrontMCP: Server-Side Request Forgery (SSRF) in the OpenAPI adapter spec-change poller",
"details" : "## Summary\n\nThe OpenAPI adapter's spec-change **poller** (`OpenApiSpecPoller`) re-fetched the\nconfigured spec `url` on a timer using a raw global `fetch()`, bypassing the SSRF\nguard (`safeFetch` / `assertUrlSafe`) that `OpenAPIToolGenerator.fromURL()` applies\nto the initial spec load. As a result, the pinning/DNS-resolution hardening delivered\nvia `mcp-from-openapi >= 2.5.0` (advisory GHSA-65h7-9wrw-629c) protected the initial\nload but **not** the recurring poll of the same URL. When polling is enabled against\nan untrusted or attacker-influenceable spec URL, this is an unguarded SSRF vector.\n\n## Details\n\nThe initial spec load is guarded. `OpenapiAdapter` resolves a secure `refResolution`\npolicy and passes it to the guarded loader:\n\n```ts\n// libs/adapters/src/openapi/openapi.adapter.ts — initializeGenerator()\nreturn await OpenAPIToolGenerator.fromURL(this.options.url, {\n // ...\n followRedirects: this.options.loadOptions?.followRedirects ?? false,\n refResolution, // secure default: external $refs off, internal targets blocked\n});\n```\n\nBut the poller — which re-fetches **the same URL** on every interval — did not:\n\n```ts\n// libs/adapters/src/openapi/openapi-spec-poller.ts — doFetch() (vulnerable, <= 1.5.5)\nconst controller = new AbortController();\nconst timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs);\ntry {\n const response = await fetch(this.url, { // <-- raw global fetch, no SSRF guard\n headers,\n signal: controller.signal,\n });\n // ...hash the body, fire onChanged...\n}\n```\n\nBecause `doFetch()` never called `safeFetch`, none of the guard's protections applied\nto the polled request:\n\n- no allow-list / block-list enforcement (`allowedHosts` / `blockedHosts`);\n- no internal/private/loopback/link-local/CGNAT/cloud-metadata IP blocking;\n- no DNS resolution of the hostname (so a DNS name that resolves to an internal IP,\n e.g. `http://127.0.0.1.nip.io/`, was reached);\n- no connection **pinning** to the validated IP (DNS-rebinding TOCTOU);\n- no per-hop re-validation of HTTP redirects.\n\nThis is the identical threat model to `fromURL()` / external `$ref` resolution\n(GHSA-65h7-9wrw-629c), applied to a request path that the fix for that advisory did\nnot cover.\n\n## Impact\n\nA server that enables spec polling against an untrusted or attacker-influenceable\nspec URL will, on every poll interval, issue a server-side `GET` to whatever host the\nURL (or a DNS name it resolves to, or a redirect it returns) points at — including\ninternal-only addresses unreachable from the public internet. Consequences include:\n\n- reading cloud-instance metadata endpoints (e.g. `169.254.169.254`) — credential /\n token theft;\n- probing and reaching internal services and private-range hosts (internal network\n scanning);\n- DNS-rebinding to swap a public host for an internal one between validation and\n connection.\n\nThe poller issues `GET` requests only, so the primary impact is **confidentiality**\n(reaching and reading internal endpoints); the fetched body is content-hashed to\ndetect change and the subsequent tool rebuild goes back through the guarded\n`fromURL()` path.\n\n## Preconditions\n\nExploitation requires **both**:\n\n1. `polling.enabled: true` on an `OpenapiAdapter` (polling is off by default and\n requires the URL-based `url` option, not an inline `spec`); **and**\n2. the spec `url` is untrusted / attacker-influenceable (e.g. it is derived from user\n input, a tenant-supplied value, or otherwise not a fixed trusted constant), or an\n otherwise-trusted spec host is attacker-controlled or can redirect.\n\nServers that poll a fixed, trusted, first-party spec URL are not exposed in practice,\nthough they still benefit from the guard as defense-in-depth.\n\n## Proof of concept\n\n```ts\nimport { OpenapiAdapter } from '@frontmcp/adapters';\n\n// url is attacker-influenceable and points (directly, via DNS, or via redirect)\n// at an internal target; polling re-fetches it every interval.\nconst adapter = OpenapiAdapter.init({\n name: 'evil',\n url: 'http://169.254.169.254/latest/meta-data/', // or http://127.0.0.1.nip.io/...\n polling: { enabled: true, intervalMs: 5000 },\n});\n\nawait adapter.fetch(); // initial load IS guarded (blocked)\nadapter.startPolling(); // <= 1.5.5: each poll issues an UNGUARDED GET to the internal target\n```\n\nOn `<= 1.5.5` the timed poll reaches the internal address. On the patched version the\npoll fails closed (no request is made; the failure is logged) exactly as the initial\nload does.\n\n## Patch\n\nThe fix routes the poller through the same SSRF guard as the initial load, with the\nsame policy, so both paths share one DNS resolution + connection pinning and cannot\ndiverge:\n\n- `OpenApiSpecPoller.doFetch()` now calls `safeFetch(this.url, { headers, timeoutMs,\n followRedirects, ssrf })` from `mcp-from-openapi` instead of the global `fetch()`.\n- `OpenapiAdapter.startPolling()` injects the adapter's resolved policy into the\n poller: `ssrf: normalizeSsrfOptions(this.resolveRefResolution())` and\n `followRedirects: loadOptions?.followRedirects ?? false` — identical to what\n `fromURL()` receives.\n- `SpecPollerOptions` gained optional `ssrf` / `followRedirects`; standalone use of\n `OpenApiSpecPoller` defaults to the secure policy (internal targets blocked,\n redirects not followed).\n\nFiles changed:\n\n- `libs/adapters/src/openapi/openapi-spec-poller.ts`\n- `libs/adapters/src/openapi/openapi-spec-poller.types.ts`\n- `libs/adapters/src/openapi/openapi.adapter.ts`\n\nRequires `mcp-from-openapi >= 2.5.0` (already a dependency at `2.5.1`), which exports\n`safeFetch` / `normalizeSsrfOptions` and performs the resolved-IP validation and\nconnection pinning.\n\n## Remediation\n\nUpgrade `@frontmcp/adapters` to `1.5.6` or later. No configuration change is required:\npolling now inherits the same secure defaults as the initial spec load (external\ntargets blocked, redirects not followed). To poll a genuinely internal or localhost\nspec server in a trusted environment, opt in explicitly with\n`loadOptions.refResolution.allowInternalIPs: true` — the same knob that gates the\ninitial load.\n\n## Workarounds\n\nFor users who cannot upgrade immediately:\n\n- disable polling (`polling.enabled: false`) on adapters whose spec `url` is not a\n fixed, trusted, first-party value; or\n- only enable polling against spec URLs you fully control, served over HTTPS from a\n host that cannot be made to redirect to internal targets; and\n- enforce network egress controls / an allow-list at the platform layer so the server\n cannot reach internal ranges or cloud-metadata endpoints.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "@frontmcp/adapters"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.5.6"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 1.5.5"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/agentfront/frontmcp/security/advisories/GHSA-8q49-2h5h-434x"
}, {
"type" : "WEB",
"url" : "https://github.com/agentfront/frontmcp/pull/510"
}, {
"type" : "WEB",
"url" : "https://github.com/agentfront/frontmcp/commit/077201e109bf6f45dbc85c36d6bd77ded18ab13e"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/agentfront/frontmcp"
}, {
"type" : "WEB",
"url" : "https://github.com/agentfront/frontmcp/releases/tag/v1.5.6"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-24T22:40:00Z",
"nvd_published_at" : null
}
}