# OpenAPI3 version value escapes `emitterOutputDir` and overwrites YAML/JSON outside the output tree

TypeSpec openapi3 emitter interpolates an unsanitized @versioned enum value into the output filename, letting a crafted .tsp write OpenAPI YAML/JSON outside emitterOutputDir.

- Payout: Research
- Severity: high
- Program: TypeSpec
- Found by: NLx64
- Disclosed: 2026-09-08
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2q42-4q24-7rgv
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2q42-4q24-7rgv
- Tags: area:oss domain:oss impact:dos impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:path-traversal

**Target:** @typespec/openapi3 emitter output filename / compiler emitFile sink
**Root cause:** The @versioned enum member value is pushed into serviceRecord.versions and interpolated as {version} into outputFile without sanitizing path separators or '..' components; resolvePath joins the template to emitterOutputDir before interpolation, so containment is never checked, and emitFile() mkdirp+writeFile without verifying the path stays under the output root.
**Steps:**
1. Declare `@versioned(Versions) namespace Svc;` with `enum Versions { v1: "../../../../../../../../../../tmp/TYPESPEC_PWNED/pwn" }` in main.tsp
2. Run the openapi3 emitter (e.g. `tsp compile . --emit @typespec/openapi3`) from the poc directory
3. The {version} token expands to the traversal string, producing a path outside tsp-output
4. emitFile() mkdirps the parent and writes OpenAPI content to /tmp/TYPESPEC_PWNED/pwn.yaml (or .json), overwriting any existing writable file
**How found:** Code review of the openapi3 emitter: traced the @versioned enum value from openapi.ts:592-608 into the interpolatePath call at 629-641, noticed resolvePath joins the template to emitterOutputDir before {version} interpolation, then followed the path to emitFile() in emitter-utils.ts:29-39 which mkdirps and writes without a containment check.
**Exploitation:** Attacker supplies a declarative .tsp file (no JS extension needed) whose versioned enum value contains traversal components and a chosen basename; when the victim compiles it with the openapi3 emitter, the emitter writes attacker-influenced OpenAPI YAML/JSON to an arbitrary writable path, corrupting or replacing that file. Content is constrained to the emitter's OpenAPI format, not arbitrary bytes.
**Insight:** When an output filename template is resolved against a base dir before variable interpolation, any spec-derived token (versions, service names, file-type) can reintroduce traversal after the join — grep emitters for interpolatePath/resolvePath ordering and test tokens with `../` and absolute paths.

