GHSA-8q49-2h5h-434x:
JavaScript 취약성 분석 및 완화
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:
// 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:
// 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$refresolution (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
GETrequests 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:
polling.enabled: trueon anOpenapiAdapter(polling is off by default and requires the URL-basedurloption, not an inlinespec); and- the spec
urlis 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
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 targetOn <= 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 callssafeFetch(this.url, { headers, timeoutMs, followRedirects, ssrf })frommcp-from-openapiinstead of the globalfetch().OpenapiAdapter.startPolling()injects the adapter's resolved policy into the poller:ssrf: normalizeSsrfOptions(this.resolveRefResolution())andfollowRedirects: loadOptions?.followRedirects ?? false— identical to whatfromURL()receives.SpecPollerOptionsgained optionalssrf/followRedirects; standalone use ofOpenApiSpecPollerdefaults to the secure policy (internal targets blocked, redirects not followed). Files changed:libs/adapters/src/openapi/openapi-spec-poller.tslibs/adapters/src/openapi/openapi-spec-poller.types.tslibs/adapters/src/openapi/openapi.adapter.tsRequiresmcp-from-openapi >= 2.5.0(already a dependency at2.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 specurlis 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.
근원: 네비디(NVD)
관련 JavaScript 취약점:
무료 취약성 평가
클라우드 보안 태세를 벤치마킹합니다
9개의 보안 도메인에서 클라우드 보안 관행을 평가하여 위험 수준을 벤치마킹하고 방어의 허점을 식별합니다.
추가 Wiz 리소스
맞춤형 데모 받기
맞춤형 데모 신청하기
"내가 본 최고의 사용자 경험은 클라우드 워크로드에 대한 완전한 가시성을 제공합니다."
"Wiz는 클라우드 환경에서 무슨 일이 일어나고 있는지 볼 수 있는 단일 창을 제공합니다."
"우리는 Wiz가 무언가를 중요한 것으로 식별하면 실제로 중요하다는 것을 알고 있습니다."