Skip to content

Commit

Permalink
fix!: default server.cors: false to disallow fetching from untruste…
Browse files Browse the repository at this point in the history
…d origins
  • Loading branch information
sapphi-red committed Jan 20, 2025
1 parent bb576ea commit 07b36d5
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 10 deletions.
9 changes: 8 additions & 1 deletion docs/config/server-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,15 @@ export default defineConfig({
## server.cors

- **Type:** `boolean | CorsOptions`
- **Default:** `false`

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.

:::warning

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.
We recommend setting a specific value rather than `true` to avoid exposing the source code to untrusted origins.

:::

## server.headers

Expand Down
6 changes: 6 additions & 0 deletions docs/guide/backend-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ If you need a custom integration, you can follow the steps in this guide to conf
```js
// vite.config.js
export default defineConfig({
server: {
cors: {
// the origin you will be accessing via browser
origin: 'http://my-backend.example.com',
},
},
build: {
// generate manifest.json in outDir
manifest: true,
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/__tests__/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ describe('preview config', () => {
'Cache-Control': 'no-store',
},
proxy: { '/foo': 'http://localhost:4567' },
cors: false,
cors: true,
})

test('preview inherits server config with default port', async () => {
Expand Down Expand Up @@ -274,7 +274,7 @@ describe('preview config', () => {
host: false,
https: false,
proxy: { '/bar': 'http://localhost:3010' },
cors: true,
cors: false,
})

test('preview overrides server config', async () => {
Expand Down
12 changes: 12 additions & 0 deletions packages/vite/src/node/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,14 @@ export interface CommonServerOptions {
/**
* Configure CORS for the dev server.
* Uses https://github.com/expressjs/cors.
*
* When enabling this option, **we recommend setting a specific value
* rather than `true`** to avoid exposing the source code to untrusted origins.
*
* Set to `true` to allow all methods from any origin, or configure separately
* using an object.
*
* @default false
*/
cors?: CorsOptions | boolean
/**
Expand All @@ -76,6 +82,12 @@ export interface CommonServerOptions {
* https://github.com/expressjs/cors#configuration-options
*/
export interface CorsOptions {
/**
* Configures the Access-Control-Allow-Origin CORS header.
*
* **We recommend setting a specific value rather than
* `true`** to avoid exposing the source code to untrusted origins.
*/
origin?:
| CorsOrigin
| ((origin: string, cb: (err: Error, origins: CorsOrigin) => void) => void)
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export async function preview(

// cors
const { cors } = config.preview
if (cors !== false) {
if (cors !== undefined && cors !== false) {
app.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
}

Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,9 +611,9 @@ export async function _createServer(
middlewares.use(timeMiddleware(root))
}

// cors (enabled by default)
// cors
const { cors } = serverConfig
if (cors !== false) {
if (cors !== undefined && cors !== false) {
middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
}

Expand Down
77 changes: 73 additions & 4 deletions playground/fs-serve/__tests__/fs-serve.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import fetch from 'node-fetch'
import { beforeAll, describe, expect, test } from 'vitest'
import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
test,
} from 'vitest'
import type { Page } from 'playwright-chromium'
import testJSON from '../safe.json'
import { isServe, page, viteTestUrl } from '~utils'
import { browser, isServe, page, viteTestUrl } from '~utils'

const getViteTestIndexHtmlUrl = () => {
const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/'
// NOTE: viteTestUrl is set lazily
return viteTestUrl + srcPrefix + 'src/'
}

const stringified = JSON.stringify(testJSON)

describe.runIf(isServe)('main', () => {
beforeAll(async () => {
const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/'
await page.goto(viteTestUrl + srcPrefix + 'src/')
await page.goto(getViteTestIndexHtmlUrl())
})

test('default import', async () => {
Expand Down Expand Up @@ -113,3 +126,59 @@ describe('fetch', () => {
expect(res.headers.get('x-served-by')).toBe('vite')
})
})

describe('cross origin', () => {
const fetchStatusFromPage = async (page: Page, url: string) => {
return await page.evaluate(async (url: string) => {
try {
const res = await globalThis.fetch(url)
return res.status
} catch {
return -1
}
}, url)
}

describe('allowed for same origin', () => {
beforeEach(async () => {
await page.goto(getViteTestIndexHtmlUrl())
})

test('fetch HTML file', async () => {
const status = await fetchStatusFromPage(page, viteTestUrl + '/src/')
expect(status).toBe(200)
})

test.runIf(isServe)('fetch JS file', async () => {
const status = await fetchStatusFromPage(
page,
viteTestUrl + '/src/code.js',
)
expect(status).toBe(200)
})
})

describe('denied for different origin', async () => {
let page2: Page
beforeEach(async () => {
page2 = await browser.newPage()
await page2.goto('http://vite.dev/404')
})
afterEach(async () => {
await page2.close()
})

test('fetch HTML file', async () => {
const status = await fetchStatusFromPage(page2, viteTestUrl + '/src/')
expect(status).not.toBe(200)
})

test.runIf(isServe)('fetch JS file', async () => {
const status = await fetchStatusFromPage(
page2,
viteTestUrl + '/src/code.js',
)
expect(status).not.toBe(200)
})
})
})
1 change: 1 addition & 0 deletions playground/fs-serve/root/src/code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// code.js
1 change: 1 addition & 0 deletions playground/fs-serve/root/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ <h2>Denied</h2>
<script type="module">
import '../../entry'
import json, { msg } from '../../safe.json'
import './code.js'

function joinUrlSegments(a, b) {
if (!a || !b) {
Expand Down

0 comments on commit 07b36d5

Please sign in to comment.