File tree Expand file tree Collapse file tree 4 files changed +81
-1
lines changed
packages/vite/src/node/server
playground/fs-serve/__tests__ Expand file tree Collapse file tree 4 files changed +81
-1
lines changed Original file line number Diff line number Diff line change @@ -102,6 +102,7 @@ import type { DevEnvironment } from './environment'
102102import { hostValidationMiddleware } from './middlewares/hostCheck'
103103import { rejectInvalidRequestMiddleware } from './middlewares/rejectInvalidRequest'
104104import { memoryFilesMiddleware } from './middlewares/memoryFiles'
105+ import { rejectNoCorsRequestMiddleware } from './middlewares/rejectNoCorsRequest'
105106
106107const usedConfigs = new WeakSet < ResolvedConfig > ( )
107108
@@ -864,8 +865,8 @@ export async function _createServer(
864865 middlewares . use ( timeMiddleware ( root ) )
865866 }
866867
867- // disallows request that contains `#` in the URL
868868 middlewares . use ( rejectInvalidRequestMiddleware ( ) )
869+ middlewares . use ( rejectNoCorsRequestMiddleware ( ) )
869870
870871 // cors
871872 const { cors } = serverConfig
Original file line number Diff line number Diff line change 11import type { Connect } from 'dep-types/connect'
22
3+ // disallows request that contains `#` in the URL
34export function rejectInvalidRequestMiddleware ( ) : Connect . NextHandleFunction {
45 // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
56 return function viteRejectInvalidRequestMiddleware ( req , res , next ) {
Original file line number Diff line number Diff line change 1+ import type { Connect } from 'dep-types/connect'
2+
3+ /**
4+ * A middleware that rejects no-cors mode requests that are not same-origin.
5+ *
6+ * We should avoid untrusted sites to load the script to avoid attacks like GHSA-4v9v-hfq4-rm2v.
7+ * This is because:
8+ * - the path of HMR patch files / entry point files can be predictable
9+ * - the HMR patch files may not include ESM syntax
10+ * (if they include ESM syntax, loading as a classic script would fail)
11+ * - the HMR runtime in the browser has the list of all loaded modules
12+ *
13+ * https://github.com/webpack/webpack-dev-server/security/advisories/GHSA-4v9v-hfq4-rm2v
14+ * https://green.sapphi.red/blog/local-server-security-best-practices#_2-using-xssi-and-modifying-the-prototype
15+ * https://green.sapphi.red/blog/local-server-security-best-practices#properly-check-the-request-origin
16+ */
17+ export function rejectNoCorsRequestMiddleware ( ) : Connect . NextHandleFunction {
18+ // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
19+ return function viteRejectNoCorsRequestMiddleware ( req , res , next ) {
20+ // While we can set Cross-Origin-Resource-Policy header instead of rejecting requests,
21+ // we choose to reject the request to be safer in case the request handler has any side-effects.
22+ if (
23+ req . headers [ 'sec-fetch-mode' ] === 'no-cors' &&
24+ req . headers [ 'sec-fetch-site' ] !== 'same-origin'
25+ ) {
26+ res . statusCode = 403
27+ res . end ( 'Cross-origin requests must be made with CORS mode enabled.' )
28+ return
29+ }
30+ return next ( )
31+ }
32+ }
Original file line number Diff line number Diff line change @@ -560,3 +560,49 @@ describe.runIf(!isServe)('preview HTML', () => {
560560 . toBe ( '404' )
561561 } )
562562} )
563+
564+ test . runIf ( isServe ) (
565+ 'load script with no-cors mode from a different origin' ,
566+ async ( ) => {
567+ const viteTestUrlUrl = new URL ( viteTestUrl )
568+
569+ // NOTE: fetch cannot be used here as `fetch` sets some headers automatically
570+ const res = await new Promise < http . IncomingMessage > ( ( resolve , reject ) => {
571+ http
572+ . get (
573+ viteTestUrl + '/src/code.js' ,
574+ {
575+ headers : {
576+ 'Sec-Fetch-Dest' : 'script' ,
577+ 'Sec-Fetch-Mode' : 'no-cors' ,
578+ 'Sec-Fetch-Site' : 'same-site' ,
579+ Origin : 'http://vite.dev' ,
580+ Host : viteTestUrlUrl . host ,
581+ } ,
582+ } ,
583+ ( res ) => {
584+ resolve ( res )
585+ } ,
586+ )
587+ . on ( 'error' , ( e ) => {
588+ reject ( e )
589+ } )
590+ } )
591+ expect ( res . statusCode ) . toBe ( 403 )
592+ const body = Buffer . concat ( await ArrayFromAsync ( res ) ) . toString ( )
593+ expect ( body ) . toBe (
594+ 'Cross-origin requests must be made with CORS mode enabled.' ,
595+ )
596+ } ,
597+ )
598+
599+ // Note: Array.fromAsync is only supported in Node.js 22+
600+ async function ArrayFromAsync < T > (
601+ asyncIterable : AsyncIterable < T > ,
602+ ) : Promise < T [ ] > {
603+ const chunks = [ ]
604+ for await ( const chunk of asyncIterable ) {
605+ chunks . push ( chunk )
606+ }
607+ return chunks
608+ }
You can’t perform that action at this time.
0 commit comments