@@ -463,3 +463,105 @@ describe("cross-site request forgery on state-changing endpoints", () => {
463463 expect ( res . status ) . toBe ( 200 ) ;
464464 } ) ;
465465} ) ;
466+
467+ describe ( "malformed Host/Origin headers" , ( ) => {
468+ const devServerPort = port1 ;
469+
470+ let server ;
471+
472+ afterEach ( async ( ) => {
473+ if ( server ) {
474+ await server . stop ( ) ;
475+ // Allow the port to be fully released before the next test
476+ await new Promise ( ( resolve ) => {
477+ setTimeout ( resolve , 100 ) ;
478+ } ) ;
479+ server = null ;
480+ }
481+ } ) ;
482+
483+ it ( "should reject a WebSocket upgrade with a malformed Origin header without crashing" , async ( ) => {
484+ const WebSocket = require ( "ws" ) ;
485+ const http = require ( "node:http" ) ;
486+
487+ const compiler = webpack ( config ) ;
488+ server = new Server (
489+ { port : devServerPort , allowedHosts : "auto" } ,
490+ compiler ,
491+ ) ;
492+
493+ await server . start ( ) ;
494+
495+ // A malformed `Origin` (invalid IPv6 literal) used to throw while being
496+ // parsed and take down the whole dev-server process.
497+ await new Promise ( ( resolve ) => {
498+ const ws = new WebSocket ( `ws://localhost:${ devServerPort } /ws` , {
499+ headers : {
500+ host : `localhost:${ devServerPort } ` ,
501+ origin : "http://[::1/" ,
502+ } ,
503+ } ) ;
504+
505+ ws . on ( "close" , resolve ) ;
506+ ws . on ( "error" , resolve ) ;
507+ } ) ;
508+
509+ // The server must still be alive: a normal request still succeeds.
510+ const status = await new Promise ( ( resolve , reject ) => {
511+ http
512+ . get ( `http://localhost:${ devServerPort } /main.js` , ( res ) => {
513+ res . resume ( ) ;
514+ resolve ( res . statusCode ) ;
515+ } )
516+ . on ( "error" , reject ) ;
517+ } ) ;
518+
519+ expect ( status ) . toBe ( 200 ) ;
520+ } ) ;
521+
522+ it ( "should reject a request with a malformed Host header without crashing" , async ( ) => {
523+ const net = require ( "node:net" ) ;
524+ const http = require ( "node:http" ) ;
525+
526+ const compiler = webpack ( config ) ;
527+ server = new Server (
528+ { port : devServerPort , allowedHosts : "auto" } ,
529+ compiler ,
530+ ) ;
531+
532+ await server . start ( ) ;
533+
534+ // A malformed `Host` (invalid IPv6 literal) sent on a plain request used to
535+ // throw while being parsed and take down the whole dev-server process. Sent
536+ // over a raw socket so the malformed value reaches the server as-is.
537+ await new Promise ( ( resolve ) => {
538+ const socket = net . connect ( devServerPort , "127.0.0.1" , ( ) => {
539+ socket . write (
540+ [
541+ "GET /main.js HTTP/1.1" ,
542+ "Host: [::1" ,
543+ "Connection: close" ,
544+ "" ,
545+ "" ,
546+ ] . join ( "\r\n" ) ,
547+ ) ;
548+ } ) ;
549+
550+ socket . on ( "data" , ( ) => { } ) ;
551+ socket . on ( "close" , resolve ) ;
552+ socket . on ( "error" , resolve ) ;
553+ } ) ;
554+
555+ // The server must still be alive: a normal request still succeeds.
556+ const status = await new Promise ( ( resolve , reject ) => {
557+ http
558+ . get ( `http://localhost:${ devServerPort } /main.js` , ( res ) => {
559+ res . resume ( ) ;
560+ resolve ( res . statusCode ) ;
561+ } )
562+ . on ( "error" , reject ) ;
563+ } ) ;
564+
565+ expect ( status ) . toBe ( 200 ) ;
566+ } ) ;
567+ } ) ;
0 commit comments