From a82d0e23cc1e400f224644c715c3b11db2391785 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:20:55 +0900 Subject: [PATCH 1/2] fix: `preview.allowedHosts` with specific values was not respected --- packages/vite/src/node/preview.ts | 2 +- .../src/node/server/middlewares/hostCheck.ts | 34 +++++++++++++------ packages/vite/src/node/server/ws.ts | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index 969c21502fd95b..5b92a8d7addc42 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -208,7 +208,7 @@ export async function preview( const { allowedHosts } = config.preview // no need to check for HTTPS as HTTPS is not vulnerable to DNS rebinding attacks if (allowedHosts !== true && !config.preview.https) { - app.use(hostCheckMiddleware(config)) + app.use(hostCheckMiddleware(config, true)) } // proxy diff --git a/packages/vite/src/node/server/middlewares/hostCheck.ts b/packages/vite/src/node/server/middlewares/hostCheck.ts index ae9cb0858fccc2..755c38036cfa0e 100644 --- a/packages/vite/src/node/server/middlewares/hostCheck.ts +++ b/packages/vite/src/node/server/middlewares/hostCheck.ts @@ -3,7 +3,8 @@ import type { Connect } from 'dep-types/connect' import type { ResolvedConfig } from '../../config' import type { ResolvedPreviewOptions, ResolvedServerOptions } from '../..' -const allowedHostsCache = new WeakMap>() +const allowedHostsServerCache = new WeakMap>() +const allowedHostsPreviewCache = new WeakMap>() const isFileOrExtensionProtocolRE = /^(?:file|.+-extension):/i @@ -118,48 +119,59 @@ export function isHostAllowedWithoutCache( /** * @param config resolved config + * @param isPreview whether it's for the preview server or not * @param host the value of host header. See [RFC 9110 7.2](https://datatracker.ietf.org/doc/html/rfc9110#name-host-and-authority). */ -export function isHostAllowed(config: ResolvedConfig, host: string): boolean { - if (config.server.allowedHosts === true) { +export function isHostAllowed( + config: ResolvedConfig, + isPreview: boolean, + host: string, +): boolean { + const allowedHosts = isPreview + ? config.preview.allowedHosts + : config.server.allowedHosts + if (allowedHosts === true) { return true } - if (!allowedHostsCache.has(config)) { - allowedHostsCache.set(config, new Set()) + const cache = isPreview ? allowedHostsPreviewCache : allowedHostsServerCache + if (!cache.has(config)) { + cache.set(config, new Set()) } - const allowedHosts = allowedHostsCache.get(config)! - if (allowedHosts.has(host)) { + const cachedAllowedHosts = cache.get(config)! + if (cachedAllowedHosts.has(host)) { return true } const result = isHostAllowedWithoutCache( - config.server.allowedHosts, + allowedHosts, config.additionalAllowedHosts, host, ) if (result) { - allowedHosts.add(host) + cachedAllowedHosts.add(host) } return result } export function hostCheckMiddleware( config: ResolvedConfig, + isPreview: boolean, ): Connect.NextHandleFunction { // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...` return function viteHostCheckMiddleware(req, res, next) { const hostHeader = req.headers.host - if (!hostHeader || !isHostAllowed(config, hostHeader)) { + if (!hostHeader || !isHostAllowed(config, isPreview, hostHeader)) { const hostname = hostHeader?.replace(/:\d+$/, '') const hostnameWithQuotes = JSON.stringify(hostname) + const optionName = `${isPreview ? 'preview' : 'server'}.allowedHosts` res.writeHead(403, { 'Content-Type': 'text/plain', }) res.end( `Blocked request. This host (${hostnameWithQuotes}) is not allowed.\n` + - `To allow this host, add ${hostnameWithQuotes} to \`server.allowedHosts\` in vite.config.js.`, + `To allow this host, add ${hostnameWithQuotes} to \`${optionName}\` in vite.config.js.`, ) return } diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index f5cb6b208bb77d..330b922110462f 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -167,7 +167,7 @@ export function createWebSocketServer( if (protocol === 'vite-ping') return true const hostHeader = req.headers.host - if (!hostHeader || !isHostAllowed(config, hostHeader)) { + if (!hostHeader || !isHostAllowed(config, false, hostHeader)) { return false } From 6a1045dd656bb44c234eccb309b8f334f576ca71 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:32:37 +0900 Subject: [PATCH 2/2] chore: tweak --- packages/vite/src/node/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 7d46edff56dc28..d6f878493298e1 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -862,7 +862,7 @@ export async function _createServer( const { allowedHosts } = serverConfig // no need to check for HTTPS as HTTPS is not vulnerable to DNS rebinding attacks if (allowedHosts !== true && !serverConfig.https) { - middlewares.use(hostCheckMiddleware(config)) + middlewares.use(hostCheckMiddleware(config, false)) } middlewares.use(cachedTransformMiddleware(server))