diff --git a/packages/vite/src/node/server/environments/fullBundleEnvironment.ts b/packages/vite/src/node/server/environments/fullBundleEnvironment.ts index 3f96a6428b183d..4d5d86b0fc12bc 100644 --- a/packages/vite/src/node/server/environments/fullBundleEnvironment.ts +++ b/packages/vite/src/node/server/environments/fullBundleEnvironment.ts @@ -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, diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 1b2501b280d8ab..dc0b272bf01c70 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -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() @@ -929,7 +928,6 @@ export async function _createServer( } middlewares.use(rejectInvalidRequestMiddleware()) - middlewares.use(rejectNoCorsRequestMiddleware()) // cors const { cors } = serverConfig diff --git a/packages/vite/src/node/server/middlewares/rejectNoCorsRequest.ts b/packages/vite/src/node/server/middlewares/rejectNoCorsRequest.ts deleted file mode 100644 index a45de17486399d..00000000000000 --- a/packages/vite/src/node/server/middlewares/rejectNoCorsRequest.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Connect } from '#dep-types/connect' - -/** - * A middleware that rejects no-cors mode requests that are not same-origin. - * - * We should avoid untrusted sites to load the script to avoid attacks like GHSA-4v9v-hfq4-rm2v. - * This is because: - * - the path of HMR patch files / entry point files can be predictable - * - the HMR patch files may not include ESM syntax - * (if they include ESM syntax, loading as a classic script would fail) - * - the HMR runtime in the browser has the list of all loaded modules - * - * 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 - */ -export function rejectNoCorsRequestMiddleware(): Connect.NextHandleFunction { - // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...` - return function viteRejectNoCorsRequestMiddleware(req, res, next) { - // While we can set Cross-Origin-Resource-Policy header instead of rejecting requests, - // we choose to reject the request to be safer in case the request handler has any side-effects. - if ( - req.headers['sec-fetch-mode'] === 'no-cors' && - req.headers['sec-fetch-site'] !== 'same-origin' && - // we only need to block classic script requests - req.headers['sec-fetch-dest'] === 'script' - ) { - // Send a JavaScript code instead of 403 so that the error is shown in the devtools - // If we send 403, the browser will avoid loading the body of the response - // and just show "Failed to load" error without the detailed message. - res.setHeader('Content-Type', 'text/javascript') - res.end( - `throw new Error(${JSON.stringify( - '[Vite] Cross-origin requests for classic scripts must be made with CORS mode enabled.' + - ' Make sure to set the "crossorigin" attribute on your