GHSA-8q49-2h5h-434x: FrontMCP Server-Side Request Forgery (SSRF) Vulnerability

MODERATE
CVSS 5.9
CWE-918
Published
Advisory Summary

GHSA-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

CVE Identifier N/A
GHSA Identifier GHSA-8q49-2h5h-434x
Severity MODERATE
CVSS v3.1 Base Score 5.9
CWE Classification CWE-918: Server-Side Request Forgery (SSRF)
Affected Product FrontMCP
Package Ecosystem npm
Published Date
Last Updated

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).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity None (I:N)
Availability None (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.

@frontmcp/adapters fixed: 1.5.6
npm install @frontmcp/adapters@1.5.6
Verify Direct and Transitive Dependencies

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

## 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, <= 1.5.5) const controller = new AbortController(); const timeout = setTimeout(() => 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: 'http://169.254.169.254/latest/meta-data/', // or http://127.0.0.1.nip.io/... polling: { enabled: true, intervalMs: 5000 }, }); await adapter.fetch(); // initial load IS guarded (blocked) adapter.startPolling(); // <= 1.5.5: each poll issues an UNGUARDED GET to the internal target ``` On `<= 1.5.5` the timed poll reaches the internal address. On the patched version the poll fails closed (no request is made; the failure is logged) exactly as the initial load does. ## Patch The fix routes the poller through the same SSRF guard as the initial load, with the same policy, so both paths share one DNS resolution + connection pinning and cannot diverge: - `OpenApiSpecPoller.doFetch()` now calls `safeFetch(this.url, { headers, timeoutMs, followRedirects, ssrf })` from `mcp-from-openapi` instead of the global `fetch()`. - `OpenapiAdapter.startPolling()` injects the adapter's resolved policy into the poller: `ssrf: normalizeSsrfOptions(this.resolveRefResolution())` and `followRedirects: loadOptions?.followRedirects ?? false` — identical to what `fromURL()` receives. - `SpecPollerOptions` gained optional `ssrf` / `followRedirects`; standalone use of `OpenApiSpecPoller` defaults to the secure policy (internal targets blocked, redirects not followed). Files changed: - `libs/adapters/src/openapi/openapi-spec-poller.ts` - `libs/adapters/src/openapi/openapi-spec-poller.types.ts` - `libs/adapters/src/openapi/openapi.adapter.ts` Requires `mcp-from-openapi >= 2.5.0` (already a dependency at `2.5.1`), which exports `safeFetch` / `normalizeSsrfOptions` and performs the resolved-IP validation and connection pinning. ## Remediation Upgrade `@frontmcp/adapters` to `1.5.6` or later. No configuration change is required: polling now inherits the same secure defaults as the initial spec load (external targets blocked, redirects not followed). To poll a genuinely internal or localhost spec server in a trusted environment, opt in explicitly with `loadOptions.refResolution.allowInternalIPs: true` — the same knob that gates the initial load. ## Workarounds For users who cannot upgrade immediately: - disable polling (`polling.enabled: false`) on adapters whose spec `url` is not a fixed, trusted, first-party value; or - only enable polling against spec URLs you fully control, served over HTTPS from a host that cannot be made to redirect to internal targets; and - enforce network egress controls / an allow-list at the platform layer so the server cannot reach internal ranges or cloud-metadata endpoints.

GHSA-8q49-2h5h-434x Affected FrontMCP Versions

1 component(s)
Fixed Versions:
1.5.6
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?
GHSA-8q49-2h5h-434x is a security vulnerability in FrontMCP involving Server-Side Request Forgery (SSRF) in the OpenAPI adapter spec-change poller under CWE-918: Server-Side Request Forgery (SSRF).
Which FrontMCP versions are affected?
Affected versions include releases prior to 1.5.6 across affected packages: @frontmcp/adapters.
What version fixes GHSA-8q49-2h5h-434x?
Upgrading to 1.5.6 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @frontmcp/adapters affected?
Yes, @frontmcp/adapters is affected in releases prior to 1.5.6. Upgrade your dependency to 1.5.6 or later.
What is the CVSS score of GHSA-8q49-2h5h-434x?
GHSA-8q49-2h5h-434x has a CVSS v3.1 base score of 5.9 (MODERATE) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N.
What CWE is associated with GHSA-8q49-2h5h-434x?
GHSA-8q49-2h5h-434x is classified under CWE-918: Server-Side Request Forgery (SSRF) (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.).
How can I check npm dependencies for GHSA-8q49-2h5h-434x?
Run the dependency tree command: 'npm ls @frontmcp/adapters'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "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
  }
}