VYPR
Critical severity9.6GHSA Advisory· Published Jun 1, 2026· Updated Jun 1, 2026

Vitest browser mode serves unsanitized otelCarrier query parameter as inline script

CVE-2026-47428

Description

Vitest browser mode reflects unsanitized otelCarrier query parameter into an inline script, enabling XSS and token theft leading to RCE.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Vitest browser mode reflects unsanitized `otelCarrier` query parameter into an inline script, enabling XSS and token theft leading to RCE.

Vulnerability

Vitest browser mode serves the /__vitest_test__/ endpoint, which generates an inline module script that directly interpolates the otelCarrier query parameter as JavaScript source code without sanitization [1][2]. This allows an attacker to inject arbitrary JavaScript into the page. The vulnerability exists in all versions of Vitest that support browser mode, as evidenced by the code in serverOrchestrator.ts [4] and esm-client-injector.js [3].

Exploitation

An attacker must craft a URL containing a malicious otelCarrier parameter and convince a victim to open it while the Vitest browser server is running (e.g., http://localhost:63315/__vitest_test__/?otelCarrier=(alert(1),null)). The parameter value is inserted directly into the inline script, executing arbitrary JavaScript in the origin of the Vitest server. The same page also embeds the VITEST_API_TOKEN in the DOM, which the injected script can read and exfiltrate [1][2].

Impact

Successful exploitation yields arbitrary JavaScript execution in the Vitest server origin. By stealing the VITEST_API_TOKEN, the attacker can authenticate to the Vitest WebSocket APIs. In the default local browser-mode setup, this token compromise can be chained to server-side code execution: a confirmed proof of concept uses the authenticated API to write a payload into vite.config.ts, which Vitest/Vite reloads, executing the injected config code in Node.js [1][2]. This is related in impact to GHSA-9crc-q9x8-hgqq, which covered unauthenticated cross-site WebSocket access, while this issue uses reflected same-origin script execution to bypass token protection.

Mitigation

No fixed version has been disclosed in the available references [1][2][3][4]. As a workaround, users should avoid opening untrusted URLs while the Vitest browser server is running, and consider restricting network access to the browser server port. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog as of the publication date.

AI Insight generated on Jun 1, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
18af98cee183

fix(browser): simplify orchestrator otel carrier (#10285)

https://github.com/vitest-dev/vitestHiroshi OgawaMay 7, 2026Fixed in 4.1.6via ghsa-release-walk
4 files changed · +12 5
  • packages/browser/src/node/serverOrchestrator.ts+1 1 modified
    @@ -45,7 +45,7 @@ export async function resolveOrchestrator(
         __VITEST_TYPE__: '"orchestrator"',
         __VITEST_SESSION_ID__: JSON.stringify(sessionId),
         __VITEST_TESTER_ID__: '"none"',
    -    __VITEST_OTEL_CARRIER__: url.searchParams.get('otelCarrier') ?? 'null',
    +    __VITEST_OTEL_CARRIER__: JSON.stringify(session?.otelCarrier ?? null),
         __VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(browserProject.project.getProvidedContext())),
         __VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token),
       })
    
  • packages/vitest/src/node/browser/sessions.ts+8 1 modified
    @@ -1,3 +1,4 @@
    +import type { OTELCarrier } from '../../utils/traces'
     import type { TestProject } from '../project'
     import type { BrowserServerStateSession } from '../types/browser'
     import { createDefer } from '@vitest/utils/helpers'
    @@ -15,7 +16,12 @@ export class BrowserSessions {
         this.sessions.delete(sessionId)
       }
     
    -  createSession(sessionId: string, project: TestProject, pool: { reject: (error: Error) => void }): Promise<void> {
    +  createSession(
    +    sessionId: string,
    +    project: TestProject,
    +    pool: { reject: (error: Error) => void },
    +    options?: { otelCarrier?: OTELCarrier },
    +  ): Promise<void> {
         // this promise only waits for the WS connection with the orchestrator to be established
         const defer = createDefer<void>()
     
    @@ -25,6 +31,7 @@ export class BrowserSessions {
     
         this.sessions.set(sessionId, {
           project,
    +      otelCarrier: options?.otelCarrier,
           connected: () => {
             defer.resolve()
             clearTimeout(timeout)
    
  • packages/vitest/src/node/project.ts+1 3 modified
    @@ -626,14 +626,12 @@ export class TestProject {
         const url = new URL('/__vitest_test__/', origin)
         url.searchParams.set('sessionId', sessionId)
         const otelCarrier = this.vitest._traces.getContextCarrier()
    -    if (otelCarrier) {
    -      url.searchParams.set('otelCarrier', JSON.stringify(otelCarrier))
    -    }
         this.vitest._browserSessions.sessionIds.add(sessionId)
         const sessionPromise = this.vitest._browserSessions.createSession(
           sessionId,
           this,
           pool,
    +      { otelCarrier },
         )
         const pagePromise = this.browser.provider.openPage(
           sessionId,
    
  • packages/vitest/src/node/types/browser.ts+2 0 modified
    @@ -6,6 +6,7 @@ import type { Plugin, ViteDevServer } from 'vite'
     import type { BrowserCommands, CDPSession } from 'vitest/browser'
     import type { BrowserTraceViewMode } from '../../runtime/config'
     import type { BrowserTesterOptions } from '../../types/browser'
    +import type { OTELCarrier } from '../../utils/traces'
     import type { TestProject } from '../project'
     import type { ApiConfig, ProjectConfig } from './config'
     
    @@ -309,6 +310,7 @@ export interface BrowserCommandContext {
     
     export interface BrowserServerStateSession {
       project: TestProject
    +  otelCarrier?: OTELCarrier
       connected: () => void
       fail: (v: Error) => void
     }
    

Vulnerability mechanics

Root cause

"The `otelCarrier` query parameter is inserted directly into an inline module script as JavaScript source without sanitization, enabling reflected XSS."

Attack vector

An attacker crafts a URL to the Vitest browser runner endpoint (`/__vitest_test__/`) with a malicious `otelCarrier` query parameter. The value is inserted verbatim into an inline module script on the generated page, causing arbitrary JavaScript execution in the Vitest server origin [ref_id=1][ref_id=2]. The same page also embeds `VITEST_API_TOKEN`, so the injected script can recover this token and make authenticated WebSocket API calls to `/__vitest_browser_api__`. In the default local setup, the attacker can chain this to write a payload into `vite.config.ts`, which Vitest/Vite reloads and executes in Node, achieving server-side code execution [ref_id=1][ref_id=2].

Affected code

The vulnerability is in the Vitest browser-mode server orchestrator (`packages/browser/src/node/serverOrchestrator.ts` line 48) and the client-side injector (`packages/browser/src/client/public/esm-client-injector.js` line 41). The `otelCarrier` query parameter is read from the URL and inserted directly into an inline module script without sanitization, making it executable JavaScript rather than data.

What the fix does

The advisory does not include a published patch diff, but the fix would require sanitizing or encoding the `otelCarrier` query parameter before inserting it into the inline module script so that it is treated as a data value rather than executable JavaScript source [ref_id=1][ref_id=2]. The advisory notes that the same origin also exposes `VITEST_API_TOKEN`, so preventing script injection is critical to protecting the WebSocket API token.

Preconditions

  • configVitest browser mode must be running (default on localhost:63315)
  • inputVictim must open or navigate to a crafted Vitest browser-runner URL
  • authNo authentication required; the XSS executes in the same origin as the Vitest server
  • networkAttacker must be able to deliver the crafted URL to the victim (e.g., via phishing link)

Reproduction

Start browser mode in watch mode using the official Lit example: ```sh 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: ```js otelCarrier: (alert("xss via otelCarrier"),null), ``` Loading the page triggers the alert, confirming reflected script execution in the Vitest browser runner origin [ref_id=1][ref_id=2].

Generated on Jun 1, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.