Skip to content

Commit 07b36d5

Browse files
committed
fix!: default server.cors: false to disallow fetching from untrusted origins
1 parent bb576ea commit 07b36d5

File tree

9 files changed

+106
-10
lines changed

9 files changed

+106
-10
lines changed

docs/config/server-options.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,15 @@ export default defineConfig({
135135
## server.cors
136136

137137
- **Type:** `boolean | CorsOptions`
138+
- **Default:** `false`
139+
140+
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.
141+
142+
:::warning
138143

139-
Configure CORS for the dev server. This is enabled by default and allows any origin. Pass an [options object](https://github.com/expressjs/cors#configuration-options) to fine tune the behavior or `false` to disable.
144+
We recommend setting a specific value rather than `true` to avoid exposing the source code to untrusted origins.
145+
146+
:::
140147

141148
## server.headers
142149

docs/guide/backend-integration.md

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ If you need a custom integration, you can follow the steps in this guide to conf
1111
```js
1212
// vite.config.js
1313
export default defineConfig({
14+
server: {
15+
cors: {
16+
// the origin you will be accessing via browser
17+
origin: 'http://my-backend.example.com',
18+
},
19+
},
1420
build: {
1521
// generate manifest.json in outDir
1622
manifest: true,

packages/vite/src/node/__tests__/config.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ describe('preview config', () => {
237237
'Cache-Control': 'no-store',
238238
},
239239
proxy: { '/foo': 'http://localhost:4567' },
240-
cors: false,
240+
cors: true,
241241
})
242242

243243
test('preview inherits server config with default port', async () => {
@@ -274,7 +274,7 @@ describe('preview config', () => {
274274
host: false,
275275
https: false,
276276
proxy: { '/bar': 'http://localhost:3010' },
277-
cors: true,
277+
cors: false,
278278
})
279279

280280
test('preview overrides server config', async () => {

packages/vite/src/node/http.ts

+12
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,14 @@ export interface CommonServerOptions {
6262
/**
6363
* Configure CORS for the dev server.
6464
* Uses https://github.com/expressjs/cors.
65+
*
66+
* When enabling this option, **we recommend setting a specific value
67+
* rather than `true`** to avoid exposing the source code to untrusted origins.
68+
*
6569
* Set to `true` to allow all methods from any origin, or configure separately
6670
* using an object.
71+
*
72+
* @default false
6773
*/
6874
cors?: CorsOptions | boolean
6975
/**
@@ -76,6 +82,12 @@ export interface CommonServerOptions {
7682
* https://github.com/expressjs/cors#configuration-options
7783
*/
7884
export interface CorsOptions {
85+
/**
86+
* Configures the Access-Control-Allow-Origin CORS header.
87+
*
88+
* **We recommend setting a specific value rather than
89+
* `true`** to avoid exposing the source code to untrusted origins.
90+
*/
7991
origin?:
8092
| CorsOrigin
8193
| ((origin: string, cb: (err: Error, origins: CorsOrigin) => void) => void)

packages/vite/src/node/preview.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export async function preview(
144144

145145
// cors
146146
const { cors } = config.preview
147-
if (cors !== false) {
147+
if (cors !== undefined && cors !== false) {
148148
app.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
149149
}
150150

packages/vite/src/node/server/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -611,9 +611,9 @@ export async function _createServer(
611611
middlewares.use(timeMiddleware(root))
612612
}
613613

614-
// cors (enabled by default)
614+
// cors
615615
const { cors } = serverConfig
616-
if (cors !== false) {
616+
if (cors !== undefined && cors !== false) {
617617
middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
618618
}
619619

playground/fs-serve/__tests__/fs-serve.spec.ts

+73-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
import fetch from 'node-fetch'
2-
import { beforeAll, describe, expect, test } from 'vitest'
2+
import {
3+
afterEach,
4+
beforeAll,
5+
beforeEach,
6+
describe,
7+
expect,
8+
test,
9+
} from 'vitest'
10+
import type { Page } from 'playwright-chromium'
311
import testJSON from '../safe.json'
4-
import { isServe, page, viteTestUrl } from '~utils'
12+
import { browser, isServe, page, viteTestUrl } from '~utils'
13+
14+
const getViteTestIndexHtmlUrl = () => {
15+
const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/'
16+
// NOTE: viteTestUrl is set lazily
17+
return viteTestUrl + srcPrefix + 'src/'
18+
}
519

620
const stringified = JSON.stringify(testJSON)
721

822
describe.runIf(isServe)('main', () => {
923
beforeAll(async () => {
10-
const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/'
11-
await page.goto(viteTestUrl + srcPrefix + 'src/')
24+
await page.goto(getViteTestIndexHtmlUrl())
1225
})
1326

1427
test('default import', async () => {
@@ -113,3 +126,59 @@ describe('fetch', () => {
113126
expect(res.headers.get('x-served-by')).toBe('vite')
114127
})
115128
})
129+
130+
describe('cross origin', () => {
131+
const fetchStatusFromPage = async (page: Page, url: string) => {
132+
return await page.evaluate(async (url: string) => {
133+
try {
134+
const res = await globalThis.fetch(url)
135+
return res.status
136+
} catch {
137+
return -1
138+
}
139+
}, url)
140+
}
141+
142+
describe('allowed for same origin', () => {
143+
beforeEach(async () => {
144+
await page.goto(getViteTestIndexHtmlUrl())
145+
})
146+
147+
test('fetch HTML file', async () => {
148+
const status = await fetchStatusFromPage(page, viteTestUrl + '/src/')
149+
expect(status).toBe(200)
150+
})
151+
152+
test.runIf(isServe)('fetch JS file', async () => {
153+
const status = await fetchStatusFromPage(
154+
page,
155+
viteTestUrl + '/src/code.js',
156+
)
157+
expect(status).toBe(200)
158+
})
159+
})
160+
161+
describe('denied for different origin', async () => {
162+
let page2: Page
163+
beforeEach(async () => {
164+
page2 = await browser.newPage()
165+
await page2.goto('http://vite.dev/404')
166+
})
167+
afterEach(async () => {
168+
await page2.close()
169+
})
170+
171+
test('fetch HTML file', async () => {
172+
const status = await fetchStatusFromPage(page2, viteTestUrl + '/src/')
173+
expect(status).not.toBe(200)
174+
})
175+
176+
test.runIf(isServe)('fetch JS file', async () => {
177+
const status = await fetchStatusFromPage(
178+
page2,
179+
viteTestUrl + '/src/code.js',
180+
)
181+
expect(status).not.toBe(200)
182+
})
183+
})
184+
})

playground/fs-serve/root/src/code.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// code.js

playground/fs-serve/root/src/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ <h2>Denied</h2>
5252
<script type="module">
5353
import '../../entry'
5454
import json, { msg } from '../../safe.json'
55+
import './code.js'
5556

5657
function joinUrlSegments(a, b) {
5758
if (!a || !b) {

0 commit comments

Comments
 (0)