Skip to content

CVE-2025-30208: Verified Repro With Script Download

CVE-2025-30208: Vite dev server access control can be bypassed using crafted query strings, allowing arbitrary file reads via the @fs handler when the dev server is exposed to the network.

CVE-2025-30208 is verified against vite · npm. Affected versions: >= 6.2.0 < 6.2.3, >= 6.1.0 < 6.1.2, >= 6.0.0 < 6.0.12, >= 5.0.0 < 5.4.15, < 4.5.10. Fixed in 6.2.3, 6.1.2, 6.0.12, 5.4.15, 4.5.10. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00195.

REPRO-2026-00195 vite · npm Jul 1, 2026 CVE entry ↗ .txt
Severity
MEDIUM
CVSS
5.3
Confidence
HIGH
Reproduced in
23m 11s
Tool calls
258
Spend
$4.12
01 · Overview

What Is CVE-2025-30208?

CVE-2025-30208 is a medium-severity (CVSS 5.3) access-control bypass in Vite's development server. It allows arbitrary file reads via the @fs handler when the dev server is exposed to the network. Pruva reproduced it (reproduction REPRO-2026-00195).

02 · Severity & CVSS

CVE-2025-30208 Severity & CVSS Score

CVE-2025-30208 is rated medium severity, with a CVSS base score of 5.3 out of 10.

MEDIUM threat level
5.3 / 10 CVSS base

Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.

03 · Affected Versions

Affected vite Versions

vite · npm versions >= 6.2.0 < 6.2.3, >= 6.1.0 < 6.1.2, >= 6.0.0 < 6.0.12, >= 5.0.0 < 5.4.15, < 4.5.10 are affected.

How to Reproduce CVE-2025-30208

$ pruva-verify REPRO-2026-00195
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00195/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2025-30208

Information disclosure — reproduced
  • reached the target end-to-end
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Crafted query string '?import&raw??' appended to a /@fs/<absolute-path> URL against a network-exposed Vite dev server

Attack chain
  1. Vite dev server transformMiddleware: rawRE/urlRE guard skipped (trailing '??' breaks /(\?|&)raw(?:&|$)/) so ensureServingAccess is never called; isImportRequest('?import&') true
  2. transformRequest
  3. vite:raw plugin loads out-of-root file
  4. HTTP 200 with file contents
Runnable proof: reproduction_steps.sh
Captured evidence: fixed server
How the agent worked 606 events · 258 tool calls · 23 min
23 minDuration
258Tool calls
159Reasoning steps
606Events
Agent activity over 23 min
Support
10
Repro
167
Judge
22
Variant
157
Coding
245
0:0023:11

Root Cause and Exploit Chain for CVE-2025-30208

Versions: >= 6.2.0 < 6.2.3, >= 6.1.0 < 6.1.2,

Vite's dev server exposes a /@fs/ endpoint that serves arbitrary files from the filesystem, gated by an allow-list access-control check (server.fs.allow / server.fs.strict). In packages/vite/src/node/server/middlewares/transform.ts the guard that invokes that access check only fires when rawRE.test(url) or urlRE.test(url) is true, and those regexes (/(\?|&)raw(?:&|$)/ and /(\?|&)url(?:&|$)/) require the raw/url token to be followed by & or end-of-string. By appending extra trailing query separators such as ?? (e.g. /@fs/tmp/secret.txt?import&raw??), the raw token is followed by ? instead of &/$, so the regex test returns false, the ensureServingAccess(...) access check is never called, and the guard is skipped entirely. Because isImportRequest() still matches the ?import& portion of the URL, the request nevertheless enters the transform/serve block and the out-of-root file is read and returned — bypassing server.fs.deny/allow-list enforcement. This enables remote disclosure of arbitrary local files whenever the dev server is exposed to the network (--host / server.host).

  • Package / component: npm:vite — dev server @fs file-serving middleware (packages/vite/src/node/server/middlewares/transform.ts, interaction with middlewares/static.ts ensureServingAccess).
  • Affected versions: >= 6.2.0 < 6.2.3, >= 6.1.0 < 6.1.2, >= 6.0.0 < 6.0.12, >= 5.0.0 < 5.4.15, < 4.5.10.
  • Patched versions: 6.2.3, 6.1.2, 6.0.12, 5.4.15, 4.5.10.
  • Risk level: Medium. Remote arbitrary-file read against any project whose dev server is bound to a network interface (--host). Secrets under paths such as /etc/passwd, ~/.ssh/id_rsa, .env, or any readable file reachable from the server process can be exfiltrated.
  • Precondition: The dev server must be network-exposed (server.host / --host). On localhost-only bindings the same bug is technically triggerable but the impact is local-only.

Impact Parity

  • Disclosed / claimed maximum impact: Remote disclosure of arbitrary local files outside the Vite serving allow list when the dev server is exposed to the network (an information-disclosure / arbitrary-file-read primitive).
  • Reproduced impact from this run: A real running Vite dev server (vite@6.2.2, bound via host: true) returned the full contents of an out-of-root secret file (/tmp/pruva_vite_secret_*.txt) for the crafted request /@fs/tmp/pruva_vite_secret_*.txt?import&raw?? (HTTP 200, body export default "<SECRET>\n"), while the non-crafted request to the same /@fs/ path was correctly denied with HTTP 403. The patched vite@6.2.3 returns 403 for the identical crafted request (no leak).
  • Parity: full. The disclosed arbitrary-file-read primitive was reproduced end-to-end against the real product, with a fixed-version negative control.
  • Not demonstrated: No code execution is claimed or demonstrated; this is purely an information-disclosure / access-control-bypass issue.

Root Cause

In the vulnerable code (transform.ts, parent of fix commit 80381c38d6f068b12e6e928cd3c616bd1d64803c), the access-control guard is:

// vulnerable
if (
  (rawRE.test(url) || urlRE.test(url)) &&
  !ensureServingAccess(url, server, res, next)
) {
  return
}

with (in utils.ts):

export const urlRE = /(\?|&)url(?:&|$)/
export const rawRE = /(\?|&)raw(?:&|$)/
const importQueryRE = /(\?|&)import=?(?:&|$)/
export const isImportRequest = (url: string): boolean => importQueryRE.test(url)

For the crafted URL /@fs/tmp/secret.txt?import&raw??:

  1. rawRE.test(url) → looks for ?raw / &raw followed by & or $. The &raw token is followed by ? (the trailing ??), which is neither & nor end-of-string → false. urlRE.test(url)false.
  2. The whole if condition is therefore false (short-circuits), so ensureServingAccess(...) — the only place that enforces the allow list for this code path — is never invoked, and the guard does not return.
  3. Execution falls through to the transform entry condition:
    if (
      req.headers['sec-fetch-dest'] === 'script' ||
      isJSRequest(url) ||
      isImportRequest(url) ||   // ?import& matches importQueryRE  -> true
      isCSSRequest(url) ||
      isHTMLProxy(url)
    ) { ... transformRequest(...) ... send(...) }
    
    isImportRequest(url) matches ?import& (the import token is followed by &), so the request enters the transform block. removeImportQuery(url) strips the import query, leaving ?raw, and transformRequest loads the file via the vite:raw plugin (export default <JSON.stringify(content)>). The raw plugin's load reads the file directly from disk with no serving access check, so the secret content is returned with HTTP 200.

The mismatch is the crux: the access-control guard keys off rawRE/urlRE (which require &/$ termination), while the serve path keys off isImportRequest (which only requires ?import to be &/$-terminated). The trailing ?? defeats the former but not the latter, so the request is served without ever being authorized.

The normal /@fs/tmp/secret.txt request (no ?import) does not enter the transform block; it falls through to serveRawFsMiddleware, which calls ensureServingAccess with the query-stripped pathname and correctly returns 403.

Fix (commit 80381c38d6f068b12e6e928cd3c616bd1d64803c, PR #19702; backports 315695e9d97cc6cfa7e6d9e0229fb50cdae3d9f4 (6.2), 807d7f06d33ab49c48a2a3501da3eea1906c0d41 (6.1), 92ca12dc79118bf66f2b32ff81ed09e0d0bd07ca (6.0), f234b5744d8b74c95535a7b82cc88ed2144263c1 (5/4)):

const trailingQuerySeparatorsRE = /[?&]+$/
const urlWithoutTrailingQuerySeparators = url.replace(trailingQuerySeparatorsRE, '')
if (
  (rawRE.test(urlWithoutTrailingQuerySeparators) ||
    urlRE.test(urlWithoutTrailingQuerySeparators)) &&
  !ensureServingAccess(urlWithoutTrailingQuerySeparators, server, res, next)
) {
  return
}

Stripping trailing ?/& before the regex test makes rawRE.test('/@fs/.../secret.txt?import&raw') true (now &raw is at end-of-string), so ensureServingAccess runs and denies the out-of-root file (HTTP 403). The corresponding regression tests (playground/fs-serve/__tests__/fs-serve.spec.ts) assert 403 for both ?import&raw?? and ?import&raw?&.

Reproduction Steps

  1. Script: bundle/repro/reproduction_steps.sh (self-contained, idempotent).
  2. What it does:
    • Reads bundle/project_cache_context.json and uses the durable project cache dir (prepared=true) for the Vite projects; falls back to bundle/artifacts otherwise.
    • Creates two minimal Vite projects (index.html, src/main.js, vite.config.js with server.host:true, strictPort:true, fs:{strict:true, allow:[cwd]}) and installs the vulnerable vite@6.2.2 and the patched vite@6.2.3 respectively via npm install.
    • Writes a secret file outside the project root (/tmp/pruva_vite_secret_<rand>.txt) containing a unique canary token.
    • Starts the vulnerable dev server (node .../vite/bin/vite.js, host:true) and sends:
      • GET /@fs/<secret> → expects 403 (allow-list denial, control).
      • GET /@fs/<secret>?import&raw?? → expects 200 + secret token in body (the bypass).
    • Stops the server, starts the fixed vite@6.2.3 dev server, and sends the same crafted request → expects 403 with no secret (negative control).
    • Writes bundle/repro/runtime_manifest.json (via jq) and a result summary. Exits 0 only when: vuln normal=403, vuln crafted=200 + leaked, fixed crafted=403 + not leaked.
  3. Expected evidence of reproduction: HTTP 403 for the plain /@fs/ request and HTTP 200 whose body is export default "<SECRET_TOKEN>\n" for the crafted ?import&raw?? request on vite@6.2.2; HTTP 403 (no secret) for the same crafted request on vite@6.2.3.

Evidence

  • Server logs: bundle/logs/vuln_server.log, bundle/logs/fixed_server.log (Vite startup banners: VITE v6.2.2 ready in ... ms / VITE v6.3.3...).
  • Result summary: bundle/logs/result.txt.
  • HTTP request/response artifacts:
    • bundle/artifacts/http/vuln_normal_resp.txt — 403 Restricted HTML (The request url "/tmp/pruva_vite_secret_*.txt" is outside of Vite serving allow list.).
    • bundle/artifacts/http/vuln_crafted_resp.txt200, body export default "TOP_SECRET_VITE_BYPASS_TOKEN_<rand>_CANARY\n" (secret leaked through the @fs converter).
    • bundle/artifacts/http/fixed_crafted_resp.txt — 403 Restricted HTML (patch blocks the same crafted request).
  • Runtime manifest: bundle/repro/runtime_manifest.jsonentrypoint_kind=converter_document, service_started=true, healthcheck_passed=true, target_path_reached=true, with the status codes and leak booleans recorded under evidence.
  • Key reproduced excerpt (vulnerable, crafted request):
    export default "TOP_SECRET_VITE_BYPASS_TOKEN_18017_CANARY\n"
    //# sourceMappingURL=data:application/json;base64,...
    
  • Environment: Node.js v24.15.0 / npm 11.12.1; vite@6.2.2 (vulnerable) and vite@6.2.3 (fixed) installed from npm; dev server bound to 0.0.0.0:5173.

Recommendations / Next Steps

  • Upgrade to a patched release: vite@>=6.2.3, >=6.1.2, >=6.0.12, >=5.4.15, or >=4.5.10.
  • Mitigation for those who cannot upgrade immediately: do not expose the dev server to untrusted networks (avoid --host / server.host: true), or place the dev server behind a reverse proxy that strips/rejects suspicious query strings on /@fs/ paths.
  • Testing: add a regression test asserting that every /@fs/<outside-root> request with trailing query separators (?raw??, ?import&raw??, ?import&raw?&, and combinations) is rejected with 403 — matching the upstream fs-serve playground tests added by the fix.
  • Defense-in-depth: the raw/transform plugins should not rely solely on the middleware guard; consider also enforcing isFileServingAllowed inside the load pipeline so a missed middleware check cannot expose out-of-root files.

Additional Notes

  • Idempotency: The script was executed two consecutive times; both runs exited 0 with confirmed=1 (vuln normal=403, vuln crafted=200+leaked, fixed crafted=403+no-leak). Projects are written into the durable project cache (<project_cache_dir>/vite-repro-vuln and .../vite-repro-fixed); on re-runs npm install reports up to date, so subsequent runs are fast.
  • Process management: the dev server is started with ( cd <proj> && exec node .../vite/bin/vite.js ) & so $! is the node PID and stop_server reliably terminates it (with pkill -P for any worker children) before starting the next build; port 5173 is verified free before each start.
  • Surface: the ticket classifies this as converter_document (the /@fs/ endpoint converts a file path into a served module/raw document via ?import / ?raw). The reproduction exercises that real converter endpoint over HTTP against the running Vite dev server, with a fixed-version negative control.
  • The ?raw?? (without ?import) variant: on vite@6.2.2 a bare ?raw?? does not enter the transform block for a non-JS file unless a Sec-Fetch-Dest: script header / JS-like target makes isJSRequest/script true; the canonical, header-independent bypass is ?import&raw?? (and ?import&raw?&), which are the variants covered by the upstream regression tests. The reproduction uses ?import&raw??.

CVE-2025-30208 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:58
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2025-30208 · REPRO-20
0:04
0:06
0:07
web search
0:18
0:18
extract_facts
no facts extracted
0:19
0:19
0:19
supportrepro
0:21
0:21
0:21
0:22
0:22
0:22
0:22
0:33
0:34
$node --version 2>&1; npm --version 2>&1; which node npm 2>&1; echo "---cache---"; ls -la /data/pruva/project-cache/af89ded4-a3d2-46ec-9f2d-4d80416e7091/ 2>&1; echo "---mirror---"; ls -la /data/pruva/project-cache/af89ded4-a3d2-46ec-9f2d-4d80416e7091/repo-mirrors/ 2>&1
0.3s
v24.15.0
11.12.1
/usr/bin/node
/usr/bin/npm
---cache---
total 12
drwxr-xr-x 3 vscode vscode 4096 Jul  1 22:12 .
drwxr-xr-x 9 vscode vscode 4096 Jul  1 22:12 ..
0:34
$cd /tmp && curl -sL https://github.com/vitejs/vite/commit/315695e9d97cc6cfa7e6d9e0229fb50cdae3d9f4.patch 2>&1 | head -200
0.6s
{"cwd":"/workspace/artifacts/d3b8b1ff-ef6e-491f-b652-09e2ee806e34","duration_ms":555,"exit_code":0,"stderr":"","stdout":"From 315695e9d97cc6cfa7e6d9e0229fb50cdae3d9f4 Mon Sep 17 00:00:00 2001\nFrom: =?UTF-8?q?=E7=BF=A0=20/=20green?= <green@sapphi.red>\nDate: Mon, 24 Mar 2025 18:43:29 +0900\nSubject: [PATCH] fix: backport #19702, fs raw query with query separators\n (#19704)\n\n---\n .../src/node/server/middlewares/transform.ts  | 15 +++++++-\n .../fs-serve/__tests__/fs-serve.spec.ts       | 14 +++++++\n playground/fs-serve/root/src/index.html       | 38 +++++++++++++++++++\n 3 files changed, 65 insertions(+), 2 deletions(-)\n\ndiff --git a/packages/vite/src/node/server/middlewares/transform.ts b/packages/vite/src/node/server/middlewares/transform.ts\nindex 4bc1042c636084..861c1b360d7aa1 100644\n--- a/packages/vite/src/node/server/middlewares/transform.ts\n+++ b/packages/vite/src/node/server/middlewares/transform.ts\n@@ -44,6 +44,7 @@ import { ensureServingAccess } from './static'\n const debugCache = createDebugger('vite:cache')\n \n const knownIgnoreList = new Set(['/', '/favicon.ico'])\n+const trailingQuerySeparatorsRE = /[?&]+$/\n \n export function transformMiddleware(\n   serv… [truncated]
0:42
0:46
$cd /tmp && echo "=== vite versions ===" && npm view vite@6.2.2 version 2>&1; npm view vite@6.2.3 version 2>&1; echo "=== parent of fix commit ===" && curl -sL https://github.com/vitejs/vite/commit/315695e9d97cc6cfa7e6d9e0229fb50cdae3d9f4.patch 2>&1 | head -5
4.5s
=== vite versions ===
6.2.2
npm notice
npm notice New minor version of npm available! 11.12.1 -> 11.18.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.18.0
npm notice To update run: npm install -g npm@11.18.0
npm notice
6.2.3
0:48
$cd /tmp && echo "=== other fix commits ===" && for c in 80381c38d6f068b12e6e928cd3c616bd1d64803c 807d7f06d33ab49c48a2a3501da3eea1906c0d41 92ca12dc79118bf66f2b32ff81ed09e0d0bd07ca f234b5744d8b74c95535a7b82cc88ed2144263c1; do echo "--- $c ---"; curl -sL "https://github.com/vitejs/vite/commit/$c.patch" 2>&1 | head -8; done
1.9s
{"cwd":"/workspace/artifacts/d3b8b1ff-ef6e-491f-b652-09e2ee806e34","duration_ms":1898,"exit_code":0,"stderr":"","stdout":"=== other fix commits ===\n--- 80381c38d6f068b12e6e928cd3c616bd1d64803c ---\nFrom 80381c38d6f068b12e6e928cd3c616bd1d64803c Mon Sep 17 00:00:00 2001\nFrom: =?UTF-8?q?=E7=BF=A0=20/=20green?= <green@sapphi.red>\nDate: Mon, 24 Mar 2025 18:25:11 +0900\nSubject: [PATCH] fix: fs raw query with query separators (#19702)\n\n---\n .../src/node/server/middlewares/transform.ts  | 15 +++++++-\n .../fs-serve/__tests__/fs-serve.spec.ts       | 14 +++++++\n--- 807d7f06d33ab49c48a2a3501da3eea1906c0d41 ---\nFrom 807d7f06d33ab49c48a2a3501da3eea1906c0d41 Mon Sep 17 00:00:00 2001\nFrom: =?UTF-8?q?=E7=BF=A0=20/=20green?= <green@sapphi.red>\nDate: Mon, 24 Mar 2025 18:42:35 +0900\nSubject: [PATCH] fix: backport #19702, fs raw query with query separators\n (#19703)\n\n---\n .../src/node/server/middlewares/transform.ts  | 15 +++++++-\n--- 92ca12dc79118bf66f2b32ff81ed09e0d0bd07ca ---\nFrom 92ca12dc79118bf66f2b32ff81ed09e0d0bd07ca Mon Sep 17 00:00:00 2001\nFrom: =?UTF-8?q?=E7=BF=A0=20/=20green?= <green@sapphi.red>\nDate: Mon, 24 Mar 2025 18:25:11 +0900\nSubject: [PATCH] fix: fs raw query w… [truncated]
0:52
0:53
$cd /tmp && PARENT=$(curl -sL "https://api.github.com/repos/vitejs/vite/commits/80381c38d6f068b12e6e928cd3c616bd1d64803c" 2>&1 | jq -r '.parents[0].sha' 2>&1); echo "PARENT=$PARENT"; echo "=== transform.ts (vulnerable) ==="; curl -sL "https://raw.githubusercontent.com/vitejs/vite/$PARENT/packages/vite/src/node/server/middlewares/transform.ts" 2>&1 | sed -n '1,60p'
0.9s
{"cwd":"/workspace/artifacts/d3b8b1ff-ef6e-491f-b652-09e2ee806e34","duration_ms":882,"exit_code":0,"stderr":"","stdout":"PARENT=021443c5a11eedfb176f2c65d5fab65e9c21adfb\n=== transform.ts (vulnerable) ===\nimport path from 'node:path'\nimport fsp from 'node:fs/promises'\nimport type { Connect } from 'dep-types/connect'\nimport colors from 'picocolors'\nimport type { ExistingRawSourceMap } from 'rollup'\nimport type { ViteDevServer } from '..'\nimport {\n  createDebugger,\n  fsPathFromId,\n  injectQuery,\n  isImportRequest,\n  isJSRequest,\n  normalizePath,\n  prettifyUrl,\n  rawRE,\n  removeImportQuery,\n  removeTimestampQuery,\n  urlRE,\n} from '../../utils'\nimport { send } from '../send'\nimport { ERR_LOAD_URL, transformRequest } from '../transformRequest'\nimport { applySourcemapIgnoreList } from '../sourcemap'\nimport { isHTMLProxy } from '../../plugins/html'\nimport {\n  DEP_VERSION_RE,\n  ERR_FILE_NOT_FOUND_IN_OPTIMIZED_DEP_DIR,\n  ERR_OPTIMIZE_DEPS_PROCESSING_ERROR,\n  FS_PREFIX,\n} from '../../constants'\nimport {\n  isCSSRequest,\n  isDirectCSSRequest,\n  isDirectRequest,\n} from '../../plugins/css'\nimport { ERR_CLOSED_SERVER } from '../pluginContainer'\nimport { cleanUrl… [truncated]
0:54
0:55
$cd /tmp && echo "=== vulnerable transform.ts around ensureServingAccess ==="; curl -sL "https://raw.githubusercontent.com/vitejs/vite/021443c5a11eedfb176f2c65d5fab65e9c21adfb/packages/vite/src/node/server/middlewares/transform.ts" 2>&1 | sed -n '120,200p'; echo "=== static.ts ensureServingAccess ==="; curl -sL "https://raw.githubusercontent.com/vitejs/vite/021443c5a11eedfb176f2c65d5fab65e9c21adfb/packages/vite/src/node/server/middlewares/static.ts" 2>&1 | sed -n '1,120p'
0.6s
{"cwd":"/workspace/artifacts/d3b8b1ff-ef6e-491f-b652-09e2ee806e34","duration_ms":536,"exit_code":0,"stderr":"","stdout":"=== vulnerable transform.ts around ensureServingAccess ===\n            : normalizePath(path.resolve(server.config.root, url.slice(1)))\n          try {\n            const map = JSON.parse(\n              await fsp.readFile(sourcemapPath, 'utf-8'),\n            ) as ExistingRawSourceMap\n\n            applySourcemapIgnoreList(\n              map,\n              sourcemapPath,\n              server.config.server.sourcemapIgnoreList,\n              server.config.logger,\n            )\n\n            return send(req, res, JSON.stringify(map), 'json', {\n              headers: server.config.server.headers,\n            })\n          } catch {\n            // Outdated source map request for optimized deps, this isn't an error\n            // but part of the normal flow when re-optimizing after missing deps\n            // Send back an empty source map so the browser doesn't issue warnings\n            const dummySourceMap = {\n              version: 3,\n              file: sourcemapPath.replace(/\\.map$/, ''),\n              sources: [],\n              sourcesContent:… [truncated]
0:56
0:57
$cd /tmp && echo "=== ensureServingAccess in static.ts ==="; curl -sL "https://raw.githubusercontent.com/vitejs/vite/021443c5a11eedfb176f2c65d5fab65e9c21adfb/packages/vite/src/node/server/middlewares/static.ts" 2>&1 | sed -n '120,260p'
0.3s
{"cwd":"/workspace/artifacts/d3b8b1ff-ef6e-491f-b652-09e2ee806e34","duration_ms":309,"exit_code":0,"stderr":"","stdout":"=== ensureServingAccess in static.ts ===\n      cleanedUrl[cleanedUrl.length - 1] === '/' ||\n      path.extname(cleanedUrl) === '.html' ||\n      isInternalRequest(req.url!) ||\n      // skip url starting with // as these will be interpreted as\n      // scheme relative URLs by new URL() and will not be a valid file path\n      req.url?.startsWith('//')\n    ) {\n      return next()\n    }\n\n    const url = new URL(req.url!, 'http://example.com')\n    const pathname = decodeURI(url.pathname)\n\n    // apply aliases to static requests as well\n    let redirectedPathname: string | undefined\n    for (const { find, replacement } of server.config.resolve.alias) {\n      const matches =\n        typeof find === 'string'\n          ? pathname.startsWith(find)\n          : find.test(pathname)\n      if (matches) {\n        redirectedPathname = pathname.replace(find, replacement)\n        break\n      }\n    }\n    if (redirectedPathname) {\n      // dir is pre-normalized to posix style\n      if (redirectedPathname.startsWith(withTrailingSlash(dir))) {\n        redire… [truncated]
0:58
08 · How to Fix

How to Fix CVE-2025-30208

Upgrade vite · npm to 6.2.3, 6.1.2, 6.0.12, 5.4.15, 4.5.10 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2025-30208 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2025-30208

Which versions of Vite are affected by CVE-2025-30208, and where is it fixed?

Vite >= 6.2.0 < 6.2.3, >= 6.1.0 < 6.1.2, >= 6.0.0 < 6.0.12, >= 5.0.0 < 5.4.15, and < 4.5.10 are affected. It is fixed in 6.2.3, 6.1.2, 6.0.12, 5.4.15, and 4.5.10 respectively.

How severe is CVE-2025-30208?

Medium severity, CVSS 5.3. The impact is arbitrary file disclosure, not remote code execution, and it primarily matters when the Vite dev server is exposed beyond localhost to the network.

How can I reproduce CVE-2025-30208?

Download the verified script from this page and run it in an isolated environment against a vulnerable Vite dev server exposed to the network. Request a file outside the allowed serving list using a crafted query string like ?raw?? or ?import&raw?? on the @fs path, and confirm the file contents are returned; confirm a patched version rejects the request.
11 · References

References for CVE-2025-30208

Authoritative sources for CVE-2025-30208 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.