TL;DR

orval — the OpenAPI-to-TypeScript generator that emits axios/fetch clients, TanStack Query and SWR hooks, zod schemas and MSW mocks, at ~1.6 million weekly npm downloads (registry API, week ending 2026-09-11) — carried eleven critical (CVSS 9.3) code-injection CVEs, all reported by the same researchers (Gal3m, mrostamipoor), all fixed in 8.21.0 (published to npm 2026-07-12), and all entering the GitHub Advisory Database on 2026-09-02/03 — which is why they surfaced in a recency-sorted sweep two months after the fix. The shape is uniform: orval interpolated spec-controlled strings (paths, servers[].url, property names, parameter names, default values) into generated code without escaping, so a backtick or ${…} in a hostile OpenAPI document becomes live JavaScript. Three of the eleven fire when the generated request function is called; eight fire at import time of the generated zod or mock module — no request, no user action. If an agent or a developer ran orval against a spec they did not author, treat the generated output as attacker-supplied code.

What happened

orval reads an OpenAPI document and writes TypeScript. Several emitters used template literals or single-quoted object keys and pasted spec strings straight in. The eleven advisories, all published on GitHub 2026-07-12 (advisory date) with database entries 2026-09-02/03:

CVE GHSA Sink When it executes
CVE-2026-62681 GHSA-fg9p-mrxr-hvq7 OpenAPI path → request-URL template literal when the generated URL/request/query-key function runs
CVE-2026-62682 GHSA-88f2-fpv8-89q2 servers[].url → request-URL template literal same
CVE-2026-71867 GHSA-2w86-xfrc-g85r schema property name → computed-property key (MSW mock) when the mock factory is called
CVE-2026-71866 GHSA-6mr6-jvcr-2f25 schema property name → computed property (second sink) import/call of the generated module
CVE-2026-71865 GHSA-653q-5476-x79g query parameter name → computed property import time
CVE-2026-71864 GHSA-6437-gxhq-pqv8 header parameter name → computed property import time
CVE-2026-72717 GHSA-w727-8j6c-2rj4 schema default → zod module-level template import time
CVE-2026-71869 GHSA-2h9g-j24r-h63g array-items default → zod module-level import time
CVE-2026-71871 GHSA-8j6p-r8jg-mxqh header-parameter default → zod module-level import time
CVE-2026-71868 GHSA-3575-w9fc-c2j6 enum-typed default → zod module-level import time
CVE-2026-72716 GHSA-p4cg-3328-rvfg query-parameter default → zod module-level import time

Three of these (62681, 72717, 71867) were fetched directly for this advisory and each states affected < 8.21.0, patched 8.21.0, CVSS 9.3, the same reporters, and the same remediation author; the other eight were read from the GitHub Advisory Database listing (titles, CVE ids, dates) and carry the same publication date — confirm the individual page before relying on a specific one. GHSA-w727-8j6c-2rj4 also records the NVD publication as 2026-08-19, i.e. these are not September bugs; the September date is when GitHub's curated database picked them up.

Why import-time matters. A generated zod schema with a poisoned default of the form v${<code>}w becomes a module-level template literal, evaluated the moment anything imports the schema file — a test runner, a Next.js route handler, a Storybook story, an agent "just checking types." The URL-template variants need a call; the zod and mock variants need only import.

Who is exposed in practice. The attacker must influence the OpenAPI document. That is a realistic path in exactly the workflows this repo covers: an AI coding agent told to "generate a typed client for this API" fetches a third-party spec and runs orval on it; a monorepo consumes a partner's published spec; a CI job regenerates clients from a URL on every build. It is not a remote-network bug against a running app.

Am I affected?

# Which orval is installed / pinned?
npm ls orval 2>/dev/null | grep orval
grep -rn '"orval"' package.json */package.json 2>/dev/null
# July wave: vulnerable if < 8.21.0. September wave (see Update 2026-09-24): vulnerable if < 8.37.0. Current latest at time of writing: 8.37.0.

# Did any generated file get a template-literal or computed-key payload? (quick, imperfect heuristics on generated output)
grep -rnE '\$\{[^}]*(require|process|child_process|fetch|import)\b' src/api/ src/generated/ 2>/dev/null
grep -rnE "\['?\`|\`[^\`]*\$\{" --include='*.zod.ts' --include='*.msw.ts' -r . 2>/dev/null | grep -v node_modules | head

# Where do your specs come from? Anything fetched over the network at generate time is untrusted input.
grep -rnE 'input:\s*["'\'']https?://' orval.config.* 2>/dev/null

If you are affected

  1. Upgrade to orval ≥ 8.37.0 (8.21.0 closed the July batch; the September wave runs to 8.37.0 — see the 2026-09-24 update) and regenerate every client, schema and mock from a spec you have reviewed.
  2. If a pre-8.21.0 generation ran against a spec you did not control, diff the generated output for interpolated code and treat the environment that imported it (dev machine, CI runner, test container) as having executed untrusted code: → playbooks/if-you-ran-malicious-postinstall.md — the triage is the same as a malicious install hook.
  3. → playbooks/auditing-a-vibe-coded-repo.md for repos where an agent produced the client code.

Prevention

  • → prevention/package-vetting-checklist.md — an OpenAPI spec is an input to a code generator, so it is code; review it like a dependency before generating from it.
  • → prevention/agent-sandboxing.md — run codegen (and the tests that import its output) in the same sandbox you would give an unknown npm package; "generate a client for this URL" is a fetch-and-execute instruction.
  • Commit generated code and review the diff on regeneration; a template literal appearing in a zod default is visible in review and invisible at runtime.
  • The same bug class — spec/schema strings pasted into generated source — is worth checking in any other generator you use (openapi-typescript, openapi-generator, Prisma-adjacent schema tooling); the fix pattern is JSON.stringify on every interpolated string.

Update 2026-09-24 — a second wave: twenty more vendor advisories (2026-09-06 → 09-23) across 8.28.1 → 8.37.0, five new VulnCheck CVEs on 09-23, and the same root cause every time — a spec string pasted into generated source

The July batch above (fixed 8.21.0) was not the end. Since 2026-09-06 orval's own advisory tab has published twenty more advisories, nearly all critical or high, nearly all "import-time" code execution from an unescaped OpenAPI value, and the fixes have shipped as a rolling series of releases — 8.28.0/8.28.1 (2026-09-03), 8.29.0 (09-06), 8.30.0 (09-07), 8.31.0 (09-10), 8.32.0 (09-12), 8.33.0 (09-13), then 8.34.0–8.37.0, with 8.37.0 current on 2026-09-24 (registry time field; the GitHub releases page's summaries mis-state the year as 2024). VulnCheck, as CNA, assigned five CVEs on 2026-09-23 to the earliest of this wave, all CVSS 4.0 9.3:

CVE Component Affected → fixed (VulnCheck index)
CVE-2026-96754 @orval/hono — OpenAPI path in a single-quoted route literal (apostrophe injection, executes on import of the generated module) < 8.29.0 → 8.29.0 (vendor GHSA-g4mf-q5hw-f9j9)
CVE-2026-96755 @orval/effect — code injection 8.14.0 – 8.28.1 → 8.29.0
CVE-2026-96756 factory generation < 8.30.0 → 8.30.0
CVE-2026-96757 unescaped OpenAPI media-type key 6.7.1 – 8.28.x → 8.29.0 (vendor GHSA-4q3x-rqfw-3x8p)
CVE-2026-96758 @orval/core form-data serializer — multipart property names (vendor GHSA-jwhm-6748-j6pq) < 8.28.0 → 8.28.0

NVD carries CVE-2026-96754 and CVE-2026-96758 at 9.3 Critical (published 2026-09-23). Two later vendor advisories, fetched directly, show the wave did not stop at 8.29/8.30: GHSA-v263-cp2v-vrrx (published 09-13, CVSS 9.8, @orval/zod ≤ 8.32.0 → 8.33.0, reporter zx / @manus-pi) — resolveZodType allow-listed array item types but "returns raw, unvalidated strings for scalar types," which are then "emitted directly into generated source code as method names," so a JavaScript payload in a schema's type field runs the moment the generated module is imported or bundled, "requiring no authentication or special configuration"; and GHSA-w4x4-mpp4-4854 (published 09-23, CVSS 9.8, @orval/core ≤ 8.36.0 → 8.37.0, reporter zx / @manus-use) — buildPrimitivePayload used bare String(enumValues[0]) instead of JSON.stringify for numeric/boolean enum members in the factory generator, import-time again, but gated on the opt-in output.factoryMethods. The other advisories in the vendor listing (titles as published): the mutationInvalidates path-key predicate and the combine generator's schema names (09-10, both critical); @orval/mock enum generation and array-items const (09-12, critical); @orval/fetch and @orval/axios response-status keys (09-13, high); @orval/mock (MSW) status keys and the zod/effect/faker numeric-length constraints (09-07, critical); date defaults and numeric const values (09-07, high); faker enum type-confusion, @orval/fetch content-type keys, @orval/hono single quotes, a three-generator import-time RCE (09-06, high); an externalRefs.allow bypass through HTTP redirects (09-07, moderate); and a build-time DoS in @orval/mock (09-18).

What changed in the guidance. "Upgrade to ≥ 8.21.0" is no longer sufficient — upgrade to 8.37.0 or later, and expect more: two different researchers have now fuzzed the same sink class across the generator and the maintainers have been fixing them one emitter at a time for three weeks (the reporter handle pattern suggests AI-assisted discovery, which is consistent with the pace). The practical rule stands and is now stronger: an OpenAPI document you did not write is untrusted code; regenerate only from reviewed specs, in a sandbox, and diff the generated output. orval pulled 1,635,805 downloads in the week to 2026-09-21 — the population that runs orval in CI against a partner's spec URL on every build is the exposure.

Sources