From 639d2551471fb2ef5d778c01cf801d57932aa7e6 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Tue, 3 Jun 2025 16:45:12 +0300 Subject: [PATCH 1/3] fix: respect the `allowedHosts` option for cross-origin header check --- lib/Server.js | 7 ++ test/e2e/cross-origin-request.test.js | 106 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/lib/Server.js b/lib/Server.js index ff25797b34..eb84b2df75 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -1986,6 +1986,13 @@ class Server { const headers = /** @type {{ [key: string]: string | undefined }} */ (req.headers); + const headerName = headers[":authority"] ? ":authority" : "host"; + + if (this.isValidHost(headers, headerName)) { + next(); + return; + } + if ( headers["sec-fetch-mode"] === "no-cors" && headers["sec-fetch-site"] === "cross-site" diff --git a/test/e2e/cross-origin-request.test.js b/test/e2e/cross-origin-request.test.js index 7d511c51ee..65de9cf3f2 100644 --- a/test/e2e/cross-origin-request.test.js +++ b/test/e2e/cross-origin-request.test.js @@ -37,6 +37,7 @@ describe("cross-origin requests", () => { htmlServer.listen(htmlServerPort, htmlServerHost); const { page, browser } = await runBrowser(); + try { const pageErrors = []; @@ -91,6 +92,111 @@ describe("cross-origin requests", () => { htmlServer.listen(htmlServerPort, htmlServerHost); const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + + page.on("pageerror", (error) => { + pageErrors.push(error); + }); + + const scriptTagRequest = page.waitForResponse( + `http://localhost:${devServerPort}/main.js`, + ); + + await page.goto(`http://${htmlServerHost}:${htmlServerPort}`); + + const response = await scriptTagRequest; + + expect(response.status()).toBe(200); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + htmlServer.close(); + } + }); + + it("should return 200 for cross-origin no-cors non-module script tag requests with the 'allowedHost' option and 'all' value", async () => { + const compiler = webpack(config); + const devServerOptions = { + port: devServerPort, + allowedHosts: "all", + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + // Start a separate server for serving the HTML file + const http = require("http"); + const htmlServer = http.createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/html" }); + res.end(` + + + + + + + `); + }); + htmlServer.listen(htmlServerPort, htmlServerHost); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + + page.on("pageerror", (error) => { + pageErrors.push(error); + }); + + const scriptTagRequest = page.waitForResponse( + `http://localhost:${devServerPort}/main.js`, + ); + + await page.goto(`http://${htmlServerHost}:${htmlServerPort}`); + + const response = await scriptTagRequest; + + expect(response.status()).toBe(200); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + htmlServer.close(); + } + }); + + it("should return 200 for cross-origin no-cors non-module script tag requests with the `allowedHost` option and the `localhost` value", async () => { + const compiler = webpack(config); + const devServerOptions = { + port: devServerPort, + allowedHosts: ["localhost"], + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + // Start a separate server for serving the HTML file + const http = require("http"); + const htmlServer = http.createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/html" }); + res.end(` + + + + + + + `); + }); + htmlServer.listen(htmlServerPort, htmlServerHost); + + const { page, browser } = await runBrowser(); + try { const pageErrors = []; From 8dde4acab9b59d653f5899c775458774b35d8813 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Tue, 3 Jun 2025 17:32:02 +0300 Subject: [PATCH 2/3] fix: respect the `allowedHosts` option for cross-origin header check --- lib/Server.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/Server.js b/lib/Server.js index eb84b2df75..49906ece2a 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -1988,7 +1988,7 @@ class Server { (req.headers); const headerName = headers[":authority"] ? ":authority" : "host"; - if (this.isValidHost(headers, headerName)) { + if (this.isValidHost(headers, headerName, false)) { next(); return; } @@ -3173,9 +3173,10 @@ class Server { * @private * @param {{ [key: string]: string | undefined }} headers * @param {string} headerToCheck + * @param {boolean} validateHost * @returns {boolean} */ - isValidHost(headers, headerToCheck) { + isValidHost(headers, headerToCheck, validateHost = true) { if (this.options.allowedHosts === "all") { return true; } @@ -3217,12 +3218,13 @@ class Server { // For convenience, always allow localhost (hostname === 'localhost') // and its subdomains (hostname.endsWith(".localhost")). // allow hostname of listening address (hostname === this.options.host) - const isValidHostname = - ipaddr.IPv4.isValid(hostname) || - ipaddr.IPv6.isValid(hostname) || - hostname === "localhost" || - hostname.endsWith(".localhost") || - hostname === this.options.host; + const isValidHostname = validateHost + ? ipaddr.IPv4.isValid(hostname) || + ipaddr.IPv6.isValid(hostname) || + hostname === "localhost" || + hostname.endsWith(".localhost") || + hostname === this.options.host + : false; return isValidHostname; } From 94fba09fd25db71dd0f5ac3419814675a48b4805 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Tue, 3 Jun 2025 17:35:47 +0300 Subject: [PATCH 3/3] chore: fix types --- types/lib/Server.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/types/lib/Server.d.ts b/types/lib/Server.d.ts index a8e54e2df8..db75276fe2 100644 --- a/types/lib/Server.d.ts +++ b/types/lib/Server.d.ts @@ -1357,6 +1357,7 @@ declare class Server< * @private * @param {{ [key: string]: string | undefined }} headers * @param {string} headerToCheck + * @param {boolean} validateHost * @returns {boolean} */ private isValidHost;