diff --git a/docs/config/server-options.md b/docs/config/server-options.md index fbfa4b6471a576..c43aba25d9ce5b 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -149,7 +149,7 @@ export default defineConfig({ ## server.cors - **Type:** `boolean | CorsOptions` -- **Default:** `false` +- **Default:** `{ origin: /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/ }` (allows localhost, `127.0.0.1` and `::1`) Configure CORS for the dev server. Pass an [options object](https://github.com/expressjs/cors#configuration-options) to fine tune the behavior or `true` to allow any origin. diff --git a/packages/vite/src/node/__tests__/constants.spec.ts b/packages/vite/src/node/__tests__/constants.spec.ts new file mode 100644 index 00000000000000..c7015f60104280 --- /dev/null +++ b/packages/vite/src/node/__tests__/constants.spec.ts @@ -0,0 +1,32 @@ +import { expect, test } from 'vitest' +import { defaultAllowedOrigins } from '../constants' + +test('defaultAllowedOrigins', () => { + const allowed = [ + 'http://localhost', + 'http://foo.localhost', + 'http://localhost:3000', + 'https://localhost:3000', + 'http://127.0.0.1', + 'http://[::1]', + 'http://[::1]:3000', + ] + const denied = [ + 'file:///foo', + 'http://localhost.example.com', + 'http://foo.example.com:localhost', + 'http://', + 'http://192.0.2', + 'http://[2001:db8::1]', + 'http://vite', + 'http://vite:3000', + ] + + for (const origin of allowed) { + expect(defaultAllowedOrigins.test(origin), origin).toBe(true) + } + + for (const origin of denied) { + expect(defaultAllowedOrigins.test(origin), origin).toBe(false) + } +}) diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index 414d1b6430918f..f093fb7fc5c076 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -151,3 +151,10 @@ export const wildcardHosts = new Set([ export const DEFAULT_DEV_PORT = 5173 export const DEFAULT_PREVIEW_PORT = 4173 + +// the regex to allow loopback address origins: +// - localhost domains (which will always resolve to the loopback address by RFC 6761 section 6.3) +// - 127.0.0.1 +// - ::1 +export const defaultAllowedOrigins = + /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/ diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index 1d2ea2b7281bff..77e16ed824a204 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -6,6 +6,7 @@ import connect from 'connect' import type { Connect } from 'dep-types/connect' import corsMiddleware from 'cors' import type { ResolvedServerOptions, ResolvedServerUrls } from './server' +import { DEFAULT_PREVIEW_PORT , defaultAllowedOrigins } from './constants' import type { CommonServerOptions } from './http' import { httpServerStart, @@ -18,7 +19,6 @@ import compression from './server/middlewares/compression' import { proxyMiddleware } from './server/middlewares/proxy' import { resolveHostname, resolveServerUrls, shouldServeFile } from './utils' import { printServerUrls } from './logger' -import { DEFAULT_PREVIEW_PORT } from './constants' import { resolveConfig } from './config' import type { InlineConfig, ResolvedConfig } from './config' import { hostCheckMiddleware } from './server/middlewares/hostCheck' @@ -146,8 +146,14 @@ export async function preview( // cors const { cors } = config.preview - if (cors !== undefined && cors !== false) { - app.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors)) + if (cors !== false) { + app.use( + corsMiddleware( + typeof cors === 'boolean' + ? {} + : cors ?? { origin: defaultAllowedOrigins }, + ), + ) } // host check (to prevent DNS rebinding attacks) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index ff2686cfdf8f30..20a2c9da3a56a9 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -42,7 +42,11 @@ import { } from '../optimizer' import { bindShortcuts } from '../shortcuts' import type { BindShortcutsOptions } from '../shortcuts' -import { CLIENT_DIR, DEFAULT_DEV_PORT } from '../constants' +import { + CLIENT_DIR, + DEFAULT_DEV_PORT, + defaultAllowedOrigins, +} from '../constants' import type { Logger } from '../logger' import { printServerUrls } from '../logger' import { resolveChokidarOptions } from '../watch' @@ -614,8 +618,14 @@ export async function _createServer( // cors const { cors } = serverConfig - if (cors !== undefined && cors !== false) { - middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors)) + if (cors !== false) { + middlewares.use( + corsMiddleware( + typeof cors === 'boolean' + ? {} + : cors ?? { origin: defaultAllowedOrigins }, + ), + ) } // host check (to prevent DNS rebinding attacks)