Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,16 @@ export class FullBundleDevEnvironment extends DevEnvironment {
code: typeof hmrOutput.code === 'string' ? '[code]' : hmrOutput.code,
})

this.memoryFiles.set(hmrOutput.filename, { source: hmrOutput.code })
this.memoryFiles.set(hmrOutput.filename, {
// ensure that the generated hmr patch contains ESM syntax
// this is to avoid attacks like GHSA-4v9v-hfq4-rm2v
// https://github.com/webpack/webpack-dev-server/security/advisories/GHSA-4v9v-hfq4-rm2v
// https://green.sapphi.red/blog/local-server-security-best-practices#_2-using-xssi-and-modifying-the-prototype
// https://green.sapphi.red/blog/local-server-security-best-practices#properly-check-the-request-origin
// we can also use `Cross-Origin Resource Policy` header instead of this
// but we cannot use `Sec-Fetch-*` headers as they are only sent to potentially-trustworthy origins
source: hmrOutput.code + '\n; export {}',
})
if (hmrOutput.sourcemapFilename && hmrOutput.sourcemap) {
this.memoryFiles.set(hmrOutput.sourcemapFilename, {
source: hmrOutput.sourcemap,
Expand Down
2 changes: 0 additions & 2 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ import type { DevEnvironment } from './environment'
import { hostValidationMiddleware } from './middlewares/hostCheck'
import { rejectInvalidRequestMiddleware } from './middlewares/rejectInvalidRequest'
import { memoryFilesMiddleware } from './middlewares/memoryFiles'
import { rejectNoCorsRequestMiddleware } from './middlewares/rejectNoCorsRequest'

const usedConfigs = new WeakSet<ResolvedConfig>()

Expand Down Expand Up @@ -929,7 +928,6 @@ export async function _createServer(
}

middlewares.use(rejectInvalidRequestMiddleware())
middlewares.use(rejectNoCorsRequestMiddleware())

// cors
const { cors } = serverConfig
Expand Down
42 changes: 0 additions & 42 deletions packages/vite/src/node/server/middlewares/rejectNoCorsRequest.ts

This file was deleted.

81 changes: 0 additions & 81 deletions playground/fs-serve/__tests__/commonTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,84 +599,3 @@ describe.runIf(!isServe)('preview HTML', () => {
.toBe('')
})
})

test.runIf(isServe)(
'load script with no-cors mode from a different origin',
async () => {
const viteTestUrlUrl = new URL(viteTestUrl)

// NOTE: fetch cannot be used here as `fetch` sets some headers automatically
const res = await new Promise<http.IncomingMessage>((resolve, reject) => {
http
.get(
viteTestUrl + '/src/code.js',
{
headers: {
'Sec-Fetch-Dest': 'script',
'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Site': 'same-site',
Origin: 'http://vite.dev',
Host: viteTestUrlUrl.host,
},
},
(res) => {
resolve(res)
},
)
.on('error', (e) => {
reject(e)
})
})
expect(res.statusCode).toBe(200)
const body = Buffer.concat(await ArrayFromAsync(res)).toString()
expect(body).toContain(
'Cross-origin requests for classic scripts must be made with CORS mode enabled.',
)
},
)

test.runIf(isServe)(
'load image with no-cors mode from a different origin should be allowed',
async () => {
const viteTestUrlUrl = new URL(viteTestUrl)

// NOTE: fetch cannot be used here as `fetch` sets some headers automatically
const res = await new Promise<http.IncomingMessage>((resolve, reject) => {
http
.get(
viteTestUrl + '/src/code.js',
{
headers: {
'Sec-Fetch-Dest': 'image',
'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Site': 'same-site',
Origin: 'http://vite.dev',
Host: viteTestUrlUrl.host,
},
},
(res) => {
resolve(res)
},
)
.on('error', (e) => {
reject(e)
})
})
expect(res.statusCode).not.toBe(403)
const body = Buffer.concat(await ArrayFromAsync(res)).toString()
expect(body).not.toContain(
'Cross-origin requests for classic scripts must be made with CORS mode enabled.',
)
},
)

// Note: Array.fromAsync is only supported in Node.js 22+
async function ArrayFromAsync<T>(
asyncIterable: AsyncIterable<T>,
): Promise<T[]> {
const chunks = []
for await (const chunk of asyncIterable) {
chunks.push(chunk)
}
return chunks
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,22 @@ if (isBuild) {
// BUNDLED -> GENERATING_HMR_PATCH -> BUNDLED
test('generate hmr patch', async () => {
await expect.poll(() => page.textContent('.hmr')).toBe('hello')
const hmrPatchFileRes = page.waitForResponse(/\/hmr_patch_\d\.js$/)
editFile('hmr.js', (code) =>
code.replace("const foo = 'hello'", "const foo = 'hello1'"),
)
await expect.poll(() => page.textContent('.hmr')).toBe('hello1')

editFile('hmr.js', (code) =>
code.replace("const foo = 'hello1'", "const foo = 'hello'"),
)
try {
await expect.poll(() => page.textContent('.hmr')).toBe('hello1')

// ensure that the generated hmr patch contains ESM syntax
// so that it's not possible to load it in a <script> tag without type="module"
// which would allow cross origin reads
expect(await (await hmrPatchFileRes).text()).toMatch(/export\s*\{\}/)
} finally {
editFile('hmr.js', (code) =>
code.replace("const foo = 'hello1'", "const foo = 'hello'"),
)
}
await expect.poll(() => page.textContent('.hmr')).toContain('hello')
await expect.poll(() => page.textContent('.asset')).toMatch(assetUrl)
})
Expand Down