Skip to content

Commit dfea38f

Browse files
committed
fix!: default server.cors: false to disallow fetching from untrusted origins
1 parent 5da6895 commit dfea38f

File tree

9 files changed

+106
-10
lines changed

9 files changed

+106
-10
lines changed

Diff for: docs/config/server-options.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,15 @@ export default defineConfig({
137137
## server.cors
138138

139139
- **Type:** `boolean | CorsOptions`
140+
- **Default:** `false`
141+
142+
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.
143+
144+
:::warning
140145

141-
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.
146+
We recommend setting a specific value rather than `true` to avoid exposing the source code to untrusted origins.
147+
148+
:::
142149

143150
## server.headers
144151

Diff for: docs/guide/backend-integration.md

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

Diff for: packages/vite/src/node/__tests__/config.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ describe('preview config', () => {
249249
'Cache-Control': 'no-store',
250250
},
251251
proxy: { '/foo': 'http://localhost:4567' },
252-
cors: false,
252+
cors: true,
253253
})
254254

255255
test('preview inherits server config with default port', async () => {
@@ -285,7 +285,7 @@ describe('preview config', () => {
285285
open: false,
286286
host: false,
287287
proxy: { '/bar': 'http://localhost:3010' },
288-
cors: true,
288+
cors: false,
289289
})
290290

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

Diff for: packages/vite/src/node/http.ts

+12
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,14 @@ export interface CommonServerOptions {
5959
/**
6060
* Configure CORS for the dev server.
6161
* Uses https://github.com/expressjs/cors.
62+
*
63+
* When enabling this option, **we recommend setting a specific value
64+
* rather than `true`** to avoid exposing the source code to untrusted origins.
65+
*
6266
* Set to `true` to allow all methods from any origin, or configure separately
6367
* using an object.
68+
*
69+
* @default false
6470
*/
6571
cors?: CorsOptions | boolean
6672
/**
@@ -73,6 +79,12 @@ export interface CommonServerOptions {
7379
* https://github.com/expressjs/cors#configuration-options
7480
*/
7581
export interface CorsOptions {
82+
/**
83+
* Configures the Access-Control-Allow-Origin CORS header.
84+
*
85+
* **We recommend setting a specific value rather than
86+
* `true`** to avoid exposing the source code to untrusted origins.
87+
*/
7688
origin?:
7789
| CorsOrigin
7890
| ((

Diff for: packages/vite/src/node/preview.ts

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

185185
// cors
186186
const { cors } = config.preview
187-
if (cors !== false) {
187+
if (cors !== undefined && cors !== false) {
188188
app.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
189189
}
190190

Diff for: packages/vite/src/node/server/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -847,9 +847,9 @@ export async function _createServer(
847847
middlewares.use(timeMiddleware(root))
848848
}
849849

850-
// cors (enabled by default)
850+
// cors
851851
const { cors } = serverConfig
852-
if (cors !== false) {
852+
if (cors !== undefined && cors !== false) {
853853
middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
854854
}
855855

Diff for: 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+
})

Diff for: playground/fs-serve/root/src/code.js

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

Diff for: 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)