{"schema":"threatline.record/2","record":{"id":"GHSA-ff5q-5r4v-fw4v","aliases":["GHSA-ff5q-5r4v-fw4v"],"summary":"Caller headers (Authorization) forwarded to cross-origin redirect targets; blockedHosts bypass with trailing dot","details":"Administrative transfer of the saved October 16, 2026 private report. Version/latest and novelty statements below reflect that original review date, not a fresh October 19 check. No new tests were run for this submission; all original preconditions and evidence limitations below remain applicable. Please coordinate any CVE and publication privately here; no separate new CVE request accompanies this filing.\r\n\r\nI would like to report 2 issues in mcp-from-openapi 2.8.0 (latest, esm and cjs builds). Found with AploSec, our security scanner, and confirmed by execution.\r\n\r\nTwo separate issues in the URL fetching code. Neither is covered by GHSA-65h7-9wrw-629c or GHSA-v6ph-xcq9-qxxj, which are about SSRF to internal hosts.\r\n\r\n## Issue 1: Credentials forwarded across origins on redirect\r\n\r\n### Affected\r\n2.8.0. esm/index.mjs:3042-3066 (safeFetch; headers passed unchanged at the transport(current, {headers, ...}) call on every hop, current = new URL(location, current) with no origin check) and the cjs build at index.js:3130-3155. fromURL sets followRedirects ?? true (esm:3291).\r\n\r\n### Root cause\r\nThe manual redirect loop re-sends the caller's headers (Authorization, Cookie) to any origin. Plain fetch strips Authorization on cross-origin redirects; this loop does not. The library's own $ref reader intentionally omits headers for this reason.\r\n\r\n### Reproduction\r\npoc-mcp-from-openapi.mjs. The first hop is the control (the intended host receives the token). The second hop is a different origin and also receives it.\r\n\r\n```\r\nREDIRECT hops (host,url,Authorization): [[\"a.test\",\"/spec.json\",\"Bearer SECRET-TOKEN\"],[\"b.test:9002\",\"/spec.json\",\"Bearer SECRET-TOKEN\"]]\r\n```\r\n\r\n### Impact and preconditions\r\nA spec host with an open redirect, or one that is compromised or untrusted, harvests the bearer token the caller supplied for the private spec. It requires the caller to pass headers to fromURL. Medium-low.\r\n\r\n### Suggested fix\r\nDrop Authorization, Cookie and Proxy-Authorization (or all caller headers) when the redirect target origin differs, or default followRedirects to false when headers are set.\r\n\r\n### CVSS 3.1 estimate\r\nAV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N = 3.7 (5.9 if the tokens are high-privilege)\r\n\r\n## Issue 2: blockedHosts bypass via trailing dot (low)\r\n\r\n### Affected\r\n2.8.0. esm/index.mjs:2886, 2888 and 2921 (blockedHosts.includes(hostname) with hostname from new URL(), which keeps the trailing root dot); cjs index.js:2974, 2976 and 3009.\r\n\r\n### Root cause\r\nExact string match with no normalisation of a trailing dot, so a denylist entry never matches the FQDN form of the same host. With allowInternalIPs: true, blockedHosts is the only host guard. With it false, only public, name-blocked hosts are affected because the IP check still applies. A case-mismatch variant (['Metadata.Internal']) is an operator configuration error and I am not reporting it.\r\n\r\n### Reproduction\r\nThe PoC uses a DNS stub that answers any A query with 127.0.0.1, like real DNS treating host. and host the same:\r\n\r\n```\r\nblockedHosts=[b.test] http://b.test:9002/spec.json  => BLOCKED: Host \"b.test\" is blocked       (control)\r\nblockedHosts=[b.test] http://b.test.:9002/spec.json => FETCHED, server saw 1 request(s)         (attack)\r\n```\r\n\r\n### Impact and preconditions\r\nThe denylist can be evaded by appending a dot. This is a defense-in-depth gap: it needs an operator who relies on blockedHosts and an attacker who can supply the spec or $ref URL.\r\n\r\n### Suggested fix\r\nStrip trailing dots and lowercase both the hostname and the configured entries before comparing (also for allowedHosts consistency). While there, consider decoding NAT64 (64:ff9b::/96), 6to4 and IPv4-compatible IPv6 forms in the IP classifier: the control forms are blocked, but these reach a connect attempt (ENETUNREACH in my test environment). I could not show data being fetched without a translating network, so I am not reporting that as a separate vulnerability.\r\n\r\n### CVSS 3.1 estimate\r\nAV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N = 3.7\r\n\r\nI am keeping this private and plan to request a CVE ID and publish details 90 days from today (2027-01-14), or earlier once a fixed release is out.\r\n\r\nI can send the full PoC scripts, test a candidate fix, or adjust the severity if you see it differently.\r\n","categories":["Unclassified"],"product":"mcp-from-openapi","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/agentfront/mcp-from-openapi/security/advisories/GHSA-ff5q-5r4v-fw4v","source_modified_at":"2026-10-20T03:22:53Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"mcp-from-openapi"},"range":"<= 2.8.0"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/agentfront/mcp-from-openapi/security/advisories/GHSA-ff5q-5r4v-fw4v","source_modified_at":"2026-10-20T03:22:53Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"mcp-from-openapi"},"version":"2.9.0"}],"cvss":3.7,"severity":"Low","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-20T03:22:53Z","modified_at":"2026-10-20T03:22:53Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/agentfront/mcp-from-openapi/security/advisories/GHSA-ff5q-5r4v-fw4v","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-20T03:22:53Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-20T03:22:53Z","content_hash":"54786825b1b241a429804ddf4cddf40a06268cdde2e00ebbb3206e8184d2a05d"}}
