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