# Risk map — vitejs/vite

> Canonical page: https://codevetter.com/unpack/vite/risk-map

# Risk map — vitejs/vite

Scanned commit 1544bb1b7ac92f775e9d0dea97954c99cc6cbd43 (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Vite's security model rests on four independent guards — the `server.fs` allow/deny list, WebSocket token authentication, host validation, and CORS origin restriction — and a bypass in any one of them exposes the developer's filesystem or dev server to a hostile web page. The code shows scar tissue from past CVEs: Windows 8.3 short-name detection, NTFS ADS colon rejection, `#`-in-URL rejection, and trailing-slash normalisation are all defensive patches with explanatory comments. Beyond security, the main structural risks are the very large monolithic modules (`css.ts` at 3685 lines, `config.ts` at 2961), the two-implementation split between classic dev and `bundledDev`, and a large surface of deprecated-but-live compatibility shims.
- `isFileLoadingAllowed` is the single chokepoint for filesystem access and carries three separate bypass mitigations: Windows 8.3 short-name paths (`PROGRA~1`, matched by `windowsShortNameSegmentRE`) are rejected outright, any `:` outside a drive letter is rejected as a possible NTFS alternate data stream, and trailing slashes are stripped before the deny check because `fs.readFile('/foo.png/')` resolves to `/foo.png`. — [packages/vite/src/node/server/middlewares/static.ts#L291-345](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/static.ts#L291-L345)
- `server.fs.deny` defaults to only six patterns — `.env`, `.env.*`, `*.{crt,pem,key,p12,pfx,cer,der}`, `.npmrc`, `.yarnrc.yml`, `**/.git/**` — so any other secret-bearing file inside an allowed root (e.g. `credentials.json`, `.aws/`, `id_rsa` without extension) is servable when `fs.strict` passes. — [packages/vite/src/node/server/index.ts#L1229-1240](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1229-L1240)
- `config.safeModulePaths` is an escape hatch checked *before* the `fs.allow` list in `isFileLoadingAllowed`, and it is populated at runtime by `DevEnvironment._registerInputsAsSafeModules()` — meaning build inputs silently widen the serving allow-list. — [packages/vite/src/node/server/middlewares/static.ts#L340-347](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/static.ts#L340-L347) [packages/vite/src/node/server/environment.ts#L225-270](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/environment.ts#L225-L270) [packages/vite/src/node/config.ts#L2213](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2213)
- `rejectInvalidRequestMiddleware` exists specifically because Node accepts `#` in request targets while the `server.fs.deny` checks assume it is absent — a 400 is returned for any URL containing `#`, and this middleware is the first in the chain. — [packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts) [packages/vite/src/node/server/index.ts#L985](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L985)
- WebSocket auth uses a per-process 72-bit token compared with `crypto.timingSafeEqual`, but the token travels as a URL query parameter (`?token=...`), which the comment acknowledges may land in server/middleware logs. — [packages/vite/src/node/server/ws.ts#L94-116](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/ws.ts#L94-L116) [packages/vite/src/node/config.ts#L2175-2181](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2175-L2181) [packages/vite/src/client/client.ts#L46-56](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/client.ts#L46-L56)
- `config.legacy.skipWebSocketTokenCheck` is a live opt-out that disables token validation entirely inside `shouldHandle`, re-opening cross-site WebSocket hijacking for anyone who sets it. — [packages/vite/src/node/server/ws.ts#L180-183](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/ws.ts#L180-L183)
- Host validation is skipped whenever `server.https` is configured (`if (allowedHosts !== true && !serverConfig.https)`), on the reasoning that HTTPS is not DNS-rebinding-vulnerable — so a misissued or self-signed HTTPS dev setup loses that guard. — [packages/vite/src/node/server/index.ts#L993-998](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L993-L998)
- The WS upgrade path allows *any* non-browser client without a token: `shouldHandle` returns `true` when `req.headers.origin` is absent, with an inline comment justifying it for backward compat — meaning any local process that can open a socket can join the HMR channel. — [packages/vite/src/node/server/ws.ts#L185-200](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/ws.ts#L185-L200)
- Shell-out surface exists in the dev server itself: `server/index.ts` imports `execSync` from `node:child_process`, and `openBrowser.ts` spawns a browser process honouring the `BROWSER`/`BROWSER_ARGS` environment variables which `loadEnv` will populate from a project's `.env` file. — [packages/vite/src/node/server/index.ts#L1](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1) [packages/vite/src/node/server/openBrowser.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/openBrowser.ts) [packages/vite/src/node/env.ts#L68-74](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/env.ts#L68-L74)
- Config loading executes arbitrary project code by design through three paths (`bundleAndLoadConfigFile`, `runnerImportConfigFile`, `nativeImportConfigFile`), and `bundleConfigFile`/`loadConfigFromBundledFile` evaluate the bundled result — so opening an untrusted repo and running `vite` is code execution. — [packages/vite/src/node/config.ts#L2470-2480](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2470-L2480) [packages/vite/src/node/config.ts#L2566-2800](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2566-L2800)
- Blast-radius hotspots by size: `plugins/css.ts` (3685 lines), `config.ts` (2961), `utils.ts` (2022), `build.ts` (1986), `plugins/html.ts` (1753), `server/index.ts` (1496), `server/pluginContainer.ts` (1332) — none of which has a same-named unit spec except `config.spec.ts`, `build.spec.ts` and `html.spec.ts`. — [packages/vite/src/node/plugins/css.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/css.ts) [packages/vite/src/node/config.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts) [packages/vite/src/node/server/pluginContainer.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/pluginContainer.ts) [packages/vite/src/node/__tests__/](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/__tests__/)
- The dev server now has two divergent implementations gated on one boolean: `config.experimental.bundledDev` swaps the entire serving strategy, skips `cachedTransformMiddleware`, skips `buildStart` at init, and short-circuits `handleHMRUpdate` with a `// TODO: support handleHotUpdate / hotUpdate` early return — so plugin HMR hooks silently do nothing in that mode. — [packages/vite/src/node/server/index.ts#L1012-1060](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1012-L1060) [packages/vite/src/node/server/index.ts#L1097-1102](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1097-L1102) [packages/vite/src/node/server/hmr.ts#L468-471](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L468-L471)
- A large deprecated-but-live compatibility layer runs on every access: `server.hot`, `server.pluginContainer`, `server.moduleGraph`, `server.transformRequest`, `server.warmupRequest`, `server.ssrLoadModule`, `server.ssrFixStacktrace`, `server.ssrRewriteStacktrace` and `server.reloadModule` all route through `warnFutureDeprecation` and a `ModuleGraph` bridging facade. — [packages/vite/src/node/server/index.ts#L620-756](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L620-L756) [packages/vite/src/node/deprecations.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/deprecations.ts) [packages/vite/src/node/server/mixedModuleGraph.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/mixedModuleGraph.ts)
- `vite optimize` is documented-dead in its own help string ('deprecated, the pre-bundle process runs automatically and does not need to be called') yet still resolves a full config and invokes `optimizeDeps(config, options.force, true)`. — [packages/vite/src/node/cli.ts#L385-420](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L385-L420)
- The preview server reuses `hostValidationMiddleware(allowedHosts, true)` and `sirv(distDir, ...)` but has a much shorter middleware chain than the dev server and no `fs.allow` equivalent, since it serves a fixed `distDir` root. — [packages/vite/src/node/preview.ts#L131-270](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/preview.ts#L131-L270)
- `server.proxy`'s `rewriteWsOrigin` deliberately rewrites the browser-supplied `Origin` header on WebSocket upgrades, with an inline warning path when headers are already sent — a knob that defeats the target server's own origin checks if enabled carelessly. — [packages/vite/src/node/server/middlewares/proxy.ts#L36-60](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/proxy.ts#L36-L60)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
