DEV Community

Cover image for Auditing the Vitest Dev Server Secret Leak (CVE-2026-112948): How to Rotate Leaked .env Tokens Safely
Muhammad Umair | DevOmniTools
Muhammad Umair | DevOmniTools

Posted on Originally published at devomnitools.com

Auditing the Vitest Dev Server Secret Leak (CVE-2026-112948): How to Rotate Leaked .env Tokens Safely

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

Under specific developer configurations—specifically when running the interactive UI (vitest --ui), Browser Mode (@vitest/browser), or when enabling the remote api flag—Vitest's underlying HTTP server failed to apply Vite's default server.fs.deny rules.

The outcome: sensitive project assets that Vite ordinarily guards by default—including .env, .env.local, .npmrc, private SSL certificates, .git metadata, and Vitest's internal authentication token file (.vitest-secret-token)—could be fetched over plain HTTP by any client reachable on the network or by a malicious browser tab via cross-origin requests.

Vitest dev server.fs.deny bypass architecture diagram showing plain HTTP requests leaking .env and API secret files (CVE-2026-112948)

Here is an engineering audit of what went wrong, how to check if your workstation was exposed, and how to safely rotate compromised tokens.


The Vulnerability Breakdown: Why server.fs.deny Was Overridden

In a standard Vite configuration, server.fs.deny protects sensitive paths using built-in globs:

// Vite's built-in default deny patterns:
['.env', '.env.*', '*.{crt,pem}', '**/.git/**', '**/.npmrc']
Enter fullscreen mode Exit fullscreen mode

When Vitest 5 introduced its internal API server and UI token authentication (.vitest-secret-token), it programmatically appended its token file to the deny list. However, two underlying Vite runtime behaviors caused a silent bypass:

  1. Premature Array Overwrite: Vite only injects its default deny list when server.fs.deny is completely undefined. When Vitest assigned an array containing only ['.vitest-secret-token'], Vite's default rules were discarded rather than merged.
  2. Matcher Pre-Compilation: Vite compiles the deny list into an internal regex matcher during config resolution. Dynamic runtime mutations pushed later had zero effect on the active HTTP file serving pipeline.

If you ran vitest --ui on a local network or tested apps inside a browser with multi-tab sessions, any script capable of hitting localhost:51204 could scrape your secret keys.


Am I Affected?

The advisory confirms:

  • Affected: Vitest 5 running with --ui, @vitest/browser, or api: true in vitest.config.ts.
  • Not Affected: Headless command-line runs (vitest run or simple vitest in terminal mode) because they do not spawn an HTTP listener.

Immediate Remediation Steps

Step 1: Upgrade Vitest to Patched Release

Vitest maintainers released patched versions addressing the matcher compilation flaw. Upgrade Vitest alongside its peer dependencies:

npm install -D vitest@latest @vitest/ui@latest @vitest/browser@latest
# or with pnpm:
pnpm update vitest @vitest/ui @vitest/browser
Enter fullscreen mode Exit fullscreen mode

Step 2: Audit and Invalidate Leaked Secrets

If you ran Vitest UI on shared Wi-Fi (coworking spaces, cafes) or had active third-party tabs open during browser mode tests, treat all keys residing in your .env as compromised.

Common credentials stored in .env requiring immediate rotation:

  • Cloud provider access tokens (AWS, GCP, Cloudflare)
  • Database credentials (Postgres, Redis, MongoDB connection URIs)
  • OAuth 2.0 Client Secrets and JWT Signing Passphrases
  • Third-party API keys (Stripe, Resend, GitHub PATs)

Cryptographic token rotation workflow: revoking compromised .env tokens and generating 256-bit CSPRNG API keys and TOTP Base32 secrets offline

Step 3: Rotate Tokens with High-Entropy CSPRNG

When re-issuing API tokens or generating new application secrets, never use pseudo-random generators (Math.random()) or web services that log requests to central databases.

Use the Web Crypto API (crypto.getRandomValues) to generate cryptographically unguessable keys with adequate entropy:

/**
 * Generate a 256-bit high-entropy secret token (RFC 6750 compliant)
 */
function generateSecureToken(byteLength = 32): string {
  const bytes = new Uint8Array(byteLength);
  crypto.getRandomValues(bytes);
  return Array.from(bytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

// Example Output: 64-char Hex (256 bits of entropy)
console.log(generateSecureToken(32));
Enter fullscreen mode Exit fullscreen mode

If you need ready-to-copy Bearer tokens (sk_live_...), RFC 4648 Base32 keys (for TOTP/2FA secrets), or URL-safe Base64URL tokens without writing boilerplate code, you can use the free, client-side DevOmniTools Token Generator — all tokens are generated strictly in browser memory via CSPRNG with zero server calls.

Likewise, if you need to inspect decoded JWT headers and audience claims from your rotated tokens to verify expiration and issuer claims without sending your payload across the web, use the DevOmniTools Offline JWT Decoder.


Defensive Coding: Explicitly Guarding server.fs.deny

Even after updating, you can explicitly reinforce your vite.config.ts or vitest.config.ts to ensure sensitive files can never be served under any circumstances:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  server: {
    fs: {
      strict: true,
      deny: [
        '**/.env*',
        '**/*.pem',
        '**/*.key',
        '**/.git/**',
        '**/.npmrc',
        '**/.vitest-secret-token'
      ]
    }
  },
  test: {
    // your test settings
  }
});
Enter fullscreen mode Exit fullscreen mode

Summary Checklist

  1. [ ] Update vitest and @vitest/ui to the latest security patch.
  2. [ ] Revoke and re-issue all API tokens present in .env files accessed during Vitest UI sessions.
  3. [ ] Verify that server.fs.strict: true is configured in your project settings.
  4. [ ] Check Git history to ensure no .env files were accidentally committed during debugging.

Top comments (0)