Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f21ed0f

Browse filesBrowse files
authored
fix: handle malformed Host and Origin headers (#5699)
1 parent 80cd9ee commit f21ed0f
Copy full SHA for f21ed0f

3 files changed

+120-7Lines changed: 120 additions & 7 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file
+5Lines changed: 5 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack-dev-server": patch
3+
---
4+
5+
Handle malformed `Host` and `Origin` header values gracefully when validating requests.
Collapse file

‎lib/Server.js‎

Copy file name to clipboardExpand all lines: lib/Server.js
+13-7Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3304,14 +3304,20 @@ class Server {
33043304
}
33053305

33063306
// use the node url-parser to retrieve the hostname from the host-header.
3307-
// TODO resolve me in the next major release
3308-
// eslint-disable-next-line n/no-deprecated-api
3309-
const { hostname } = url.parse(
3307+
let hostname;
3308+
3309+
try {
33103310
// if header doesn't have scheme, add // for parsing.
3311-
/^(.+:)?\/\//.test(header) ? header : `//${header}`,
3312-
false,
3313-
true,
3314-
);
3311+
// TODO resolve me in the next major release
3312+
// eslint-disable-next-line n/no-deprecated-api
3313+
({ hostname } = url.parse(
3314+
/^(.+:)?\/\//.test(header) ? header : `//${header}`,
3315+
false,
3316+
true,
3317+
));
3318+
} catch {
3319+
return false;
3320+
}
33153321

33163322
if (hostname === null) {
33173323
return false;
Collapse file

‎test/e2e/cross-origin-request.test.js‎

Copy file name to clipboardExpand all lines: test/e2e/cross-origin-request.test.js
+102Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.