DEV Community

Muhammad Umair | DevOmniTools
Muhammad Umair | DevOmniTools

Posted on Originally published at devomnitools.com

Technical Breakdown: Vitest Dev-Server Secret Leak (CVE-2026-112948) & How to Protect Your .env

Original Technical Advisory & Research published on DevOmniTools Security Lab.

Vitest dev server.fs.deny leak architecture diagram

Executive Summary & Vulnerability Context

On October 21, 2026, the GitHub Advisory Database published CVE-2026-112948 (GHSA-p458-7748-mcp3), exposing a critical file access vulnerability in Vitest 5 dev servers.

When developers execute test suites using:

  • The interactive UI (vitest --ui)
  • Vitest Browser Mode (@vitest/browser)- Or by exposing the remote API (api: true / --api)

Vitest's underlying HTTP server inadvertently discarded Vite's default server.fs.deny array. This allowed unauthenticated HTTP requests from local network peers or malicious browser scripts to read raw project files including .env, .env.local, .npmrc, private SSL certificates, and .git/config.


Technical Root Cause: The Array Override Bug

Under standard Vite configurations, server.fs.deny protects sensitive dotfiles by default:

// Vite default protection
server: {
  fs: {
    deny: ['.env', '.env.*', '**/.git/**']
  }
}
Enter fullscreen mode Exit fullscreen mode

However, in Vitest 5, the test runner attempted to safeguard its internal token file by assigning an explicit array rather than merging it with Vite's existing globs:

// Buggy assignment in Vitest 5 core
config.server.fs.deny = ['.vitest-secret-token']; 
// Overwrote Vite's default ['.env', '.env.*', '**/.git/**']!
Enter fullscreen mode Exit fullscreen mode

Because Vite does not automatically deep-merge replaced configuration arrays, all default deny patterns were silently dropped. An attacker on the local network could fetch secrets with a single HTTP request:

# Exposed .env via Vitest dev server port
curl -i "http://localhost:51204/@fs/workspace/project/.env"```
{% endraw %}
---## Incident Response & Secret Rotation Protocol

If your engineering team ran {% raw %}`vitest --ui`{% endraw %} on shared local networks, public Wi-Fi, or during CI/CD test previews, follow this 3-step recovery protocol:

### 1. Upgrade Vitest Immediately
Upgrade your project dependencies to Vitest **>= 5.0.2** where {% raw %}`server.fs.deny`{% endraw %} is properly concatenated using array spreading.
{% raw %}


```bash
npm install -D vitest@latest```
{% endraw %}
### 2. Force Explicit Deny Configuration
Explicitly guard your secret files in {% raw %}`vite.config.ts`{% endraw %}:
{% raw %}


```ts
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    fs: {
      deny: ['.env', '.env.*', '**/.git/**', '.vitest-secret-token']
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

3. Rotate Compromised Credentials Offline

Never generate or paste replacement API keys into ad-supported cloud tools that store telemetry on third-party servers.

  • For Cryptographic Tokens & API Keys: Generate high-entropy 256-bit replacement keys directly in your browser using the DevOmniTools Offline CSPRNG Token Generator (uses local crypto.getRandomValues with zero network transmission).
  • For JWT Signing Secrets: Verify token claims, expiration timestamps, and detached JWS signatures safely in client-side memory using the DevOmniTools Offline JWT Inspector.

Conclusion & Architecture Lessons

  • Dev server UI ports must always be strictly bound to 127.0.0.1 and never exposed across shared LANs.
  • When authoring Vite plugins, never assign arrays directly to server.fs.deny - always concatenate existing entries.
  • Audit your test runners and keep local .env files out of public dev server scopes.

Published by Muhammad Umair & the DevOmniTools Research Team.

Top comments (0)