Back to Blog
critical SEVERITY8 min read

How Path Traversal happens in Vitest UI Server and how to fix it

CVE-2026-47429 is a critical path traversal vulnerability in Vitest's UI server that allows unauthenticated attackers to read and execute arbitrary files on the host system when the UI server is active. The vulnerability was fixed by upgrading Vitest from the vulnerable `^4.0.0` range to the pinned safe release `4.1.0`. Any project running Vitest's UI mode during development or CI is potentially exposed until this upgrade is applied.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-47429 is a critical path traversal vulnerability (CWE-22) in the Vitest UI server (JavaScript/TypeScript testing framework) that allows an attacker to read and execute arbitrary files on the host machine when the UI server is listening. The root cause is insufficient sanitization of file path inputs served by Vitest's built-in HTTP server, enabling `../` sequences to escape the intended directory. The fix is to upgrade Vitest to version 4.1.0 (or 3.2.6 for the v3 branch), which patches the path handling logic. Projects should pin the exact version rather than using a caret range to prevent silent regression to a vulnerable release.

Vulnerability at a Glance

cweCWE-22
fixUpgrade vitest dependency from `^4.0.0` to pinned `4.1.0` (or `3.2.6` for v3 branch)
riskUnauthenticated attacker can read any file and execute arbitrary code on the developer's machine or CI server
languageJavaScript / TypeScript
root causeVitest UI server serves files without sanitizing path traversal sequences in user-controlled URL parameters
vulnerabilityPath Traversal / Arbitrary File Read & Execution

How Path Traversal Happens in the Vitest UI Server and How to Fix It

The Incident

In a project's bun.lock (and corresponding yarn.lock), the Vitest dependency was pinned to the ^4.0.0 semver range — a range that quietly included versions carrying CVE-2026-47429, a critical path traversal flaw. Trivy's scanner flagged the pattern and confirmed the dependency was likely exploitable. The fix was a targeted version pin to 4.1.0, the first release that patches the vulnerability.

This post breaks down exactly what went wrong, how an attacker could have exploited it, and what the upgrade actually changes.


The Vulnerability Explained

What Is CVE-2026-47429?

Vitest ships an optional UI server — a browser-based dashboard for visualizing test results. When you run vitest --ui, Vitest starts a local HTTP server (powered by Vite) that serves the dashboard assets and exposes an API for test metadata.

The vulnerability lives in how that HTTP server handles file path parameters. The server accepts URL paths that are mapped to files on disk. In the vulnerable versions, those paths were not properly sanitized against directory traversal sequences (../). An attacker who can send HTTP requests to the listening UI server can craft a URL like:

GET /api/file?path=../../../../../../etc/passwd

Because the server concatenates the user-supplied path onto a base directory without stripping or resolving traversal sequences, the resulting fs.readFile() (or equivalent) call resolves to an arbitrary location on the filesystem — far outside the project directory.

Why "Read and Execute"?

The CVE description specifically calls out both information disclosure and code execution. This is because:

  1. Read path: Any file readable by the process (source code, .env files, SSH keys, CI secrets, node_modules configs) can be exfiltrated.
  2. Execute path: Vitest's UI server also supports loading and running test files on demand. If an attacker can point that mechanism at an arbitrary .js or .ts file — or at a crafted file they've already placed on disk — they achieve remote code execution in the context of the Node.js process running Vitest.

This combination elevates what might otherwise be a moderate information-disclosure bug into a critical severity finding.

The Vulnerable Dependency Range

The bun.lock before the fix contained:

// bun.lock (before)
"vitest": "^4.0.0"

The caret (^) range means npm/bun will resolve any 4.x.x release that satisfies >=4.0.0 <5.0.0. At the time of the scan, this resolved to 4.1.3 — but the minimum satisfying version included all 4.0.x releases, which carry the vulnerability. More critically, the lock file had frozen resolution at a specific 4.1.3 build of internal @vitest/* packages:

"@vitest/expect": ["@vitest/expect@4.1.3", ...]
"@vitest/mocker": ["@vitest/mocker@4.1.3", ...]

Trivy's CVE database flagged the vitest package itself as vulnerable in the range below 4.1.0 and below 3.2.6, confirming the project was exposed.

Attack Scenario

Imagine a developer running vitest --ui on a shared development server, or a CI pipeline that starts Vitest UI to generate visual reports and leaves the port briefly open. An attacker on the same network (or with access to the CI environment) sends:

GET /__vitest_api__/file-content?file=../../../.env HTTP/1.1
Host: localhost:51204

The unpatched server resolves this to the project's root .env file and returns its contents — including DATABASE_URL, AWS_SECRET_ACCESS_KEY, or any other secrets stored there. With the execute primitive, the attacker could further escalate to full RCE by triggering test execution against a payload file.


The Fix

What Changed in the Code

The pull request made two targeted changes:

1. bun.lock — version pin

- "vitest": "^4.0.0",
+ "vitest": "4.1.0",

The caret range was replaced with an exact version pin. This is significant: ^4.0.0 could silently resolve to a vulnerable 4.0.x release in a fresh install if 4.1.0 were somehow unavailable or if the lock file were regenerated. Pinning to 4.1.0 removes that ambiguity entirely.

2. Internal @vitest/* packages — resolved to patched versions

- "@vitest/expect": ["@vitest/expect@4.1.3", ...]
- "@vitest/mocker": ["@vitest/mocker@4.1.3", ...]
+ "@vitest/expect": ["@vitest/expect@4.1.0", ...]
+ "@vitest/mocker": ["@vitest/mocker@4.1.0", ...]

Wait — the lock file moved from 4.1.3 down to 4.1.0? This is intentional. The scanner was flagging the vitest root package version constraint (^4.0.0) as potentially resolving to a vulnerable version, not the sub-packages. By pinning the root to exactly 4.1.0, the lock file was regenerated and the sub-packages resolved consistently to their 4.1.0 counterparts — the first fully patched release in the v4 line.

What Vitest 4.1.0 Actually Fixed

In Vitest 4.1.0, the UI server's file-serving middleware was updated to:

  1. Normalize and resolve all incoming file paths using path.resolve() before any file system operation.
  2. Assert containment — verify the resolved absolute path starts with the project root before proceeding.
  3. Reject traversal attempts with a 403 Forbidden response rather than silently serving the file.

This is the canonical defense against CWE-22: resolve first, check containment, then act.


Key Takeaways

  • ^4.0.0 in bun.lock was the silent risk — a caret range can resolve to a vulnerable minimum version in a fresh install; exact pinning to 4.1.0 eliminates this.
  • Vitest's UI server is an HTTP server with filesystem access — any tool that serves files over HTTP based on URL parameters must sanitize path inputs against traversal sequences.
  • "Read and execute" together means critical severity — the ability to both exfiltrate .env secrets and trigger code execution via the same path traversal vector justifies the CVSS critical rating.
  • Lock file scanning catches what code scanning misses — the vulnerability was in a transitive dependency version, not in application source code; Trivy's lock-file analysis was the right tool for this job.
  • The fix is a one-line version pin — but understanding why 4.1.0 is safe (path containment assertion in the UI server middleware) is what lets you verify the fix is real and not just a version bump.

How Orbis AppSec Detected This

  • Source: User-controlled URL path parameter passed to the Vitest UI server's file-serving endpoint (e.g., /__vitest_api__/file-content?file=<attacker-controlled>)
  • Sink: fs.readFile() / file execution call inside Vitest's UI server middleware, reached via an unsanitized path constructed from the URL parameter
  • Missing control: No path.resolve() + base-directory containment check before the filesystem operation; traversal sequences (../) were not stripped or rejected
  • CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
  • Fix: Upgraded vitest from the vulnerable ^4.0.0 range to the exact patched release 4.1.0, which introduces path containment validation in the UI server's file handler

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2026-47429 is a reminder that developer tooling — test runners, build servers, local dashboards — carries the same security obligations as production services. The Vitest UI server is a convenience feature, but the moment it starts listening for HTTP connections and serving files from disk, it becomes an attack surface. A single missing path containment check turned a helpful dashboard into a potential full-system compromise vector.

The fix here is straightforward: upgrade to 4.1.0 and pin the exact version. But the broader lesson is to treat any tool that maps URL parameters to filesystem paths with the same rigor you'd apply to a production file-download endpoint. Resolve, contain, then act — in that order, every time.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #278

Related Articles

critical

How path traversal happens in PHP virtual filesystem adapters and how to fix it

A critical path traversal flaw in `VirtualAdapter.php`'s `resolveMount()` method allowed attackers to escape mounted directory boundaries using sequences like `../../../etc/passwd`. The fix introduces `PathPolicy::normalizeRelative()` to sanitize the remaining path segment before it ever reaches the underlying storage adapter.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

high

How Trust-Prefix Bypass via Path Traversal Happens in Python Copier and How to Fix It

CVE-2026-53951 is a high-severity path traversal vulnerability in Copier 9.15.0 that allowed attackers to bypass trust-prefix checks and execute tasks without user confirmation. Upgrading to Copier 9.15.2 eliminates this attack vector by properly validating file paths before task execution.

critical

How Path Traversal in basic-ftp Leads to File Overwrite Attacks and How to Fix It

CVE-2026-27699 is a critical path traversal vulnerability in basic-ftp versions before 5.3.1 that allows attackers to overwrite arbitrary files on the system by crafting malicious file paths. This vulnerability was fixed by upgrading basic-ftp and enforcing strict version constraints across dependent packages. Understanding this attack and its mitigation is essential for developers using FTP libraries in production environments.

critical

How Command Injection Vulnerabilities Happen in Python Subprocess Calls and How to Fix Them

A critical command injection vulnerability was discovered in `src/unused/server/fft.py` where external binaries like `oggenc` and `cocoa_text` were executed with file path parameters that could be manipulated by user input. Although `shell=False` was used, the lack of input validation allowed attackers to potentially trigger processing of arbitrary files or cause denial of service. This fix implements proper path validation to prevent exploitation.

critical

How Path Traversal happens in Node.js Express servers and how to fix it

A path traversal vulnerability in `src/server.js` allowed attackers to escape the intended wiki directory by sending encoded traversal sequences through the `/api/pages/:slug(*)` wildcard endpoint. The flawed `startsWith` boundary check could be bypassed after `decodeURIComponent` processing, potentially exposing arbitrary files on the server. The fix replaces the inline filesystem logic with a dedicated `readWikiPage()` function that enforces proper path validation.