Skip to content

Commit 420ee5c

Browse files
fix: keep this package's name and its refusals in the browser console
Two things the browser used to say that the middleware's runtime had no way to say for it. The console prefix: every message read `[webpack-dev-server]`, and four snapshots record exactly that, including `Invalid Host/Origin header` — a line people search for. Taking the runtime as-is would relabel all of it with a dependency's name and send anyone with a problem to the wrong repository, so the name travels with the logging option. The refusal itself: a client turned away by `allowedHosts` or the origin check is told why, and the reason is logged in the page. That needs the handshake to complete, so the upgrade is answered even for a client that will be refused and `guardHotEndpoint` closes it immediately after saying so — which is what this server has always done. Owning the `upgrade` listener is still what keeps a proxied upgrade with whoever it belongs to; `isHotEndpoint` went with the earlier attempt to refuse before the handshake, which would have made the reason unreachable. Verified in a browser: the console carries `[webpack-dev-server]`, and a client from a disallowed origin receives `{"action":"error","message":"Invalid Host/Origin header"}` and is closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
1 parent 136c0ba commit 420ee5c

2 files changed

Lines changed: 31 additions & 59 deletions

File tree

‎lib/Server.js‎

Lines changed: 24 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,37 +2455,6 @@ class Server {
24552455
});
24562456
}
24572457

2458-
/**
2459-
* Whether an upgrade was addressed to the hot endpoint.
2460-
*
2461-
* Compared the way `ws` compares it in `shouldHandle`: the raw target with
2462-
* the query string removed, case-sensitively. Normalizing here would claim
2463-
* url variants (`//ws`, `/WS`) that the socket itself refuses, which would
2464-
* take them from whoever they really belong to.
2465-
* @private
2466-
* @param {IncomingMessage} req the upgrade request
2467-
* @returns {boolean} whether it was for the hot endpoint
2468-
*/
2469-
isHotEndpoint(req) {
2470-
const webSocketServer =
2471-
/** @type {WebSocketServerConfiguration} */
2472-
(this.options.webSocketServer);
2473-
const serverOptions =
2474-
/** @type {NonNullable<WebSocketServerConfiguration["options"]>} */
2475-
(webSocketServer && webSocketServer.options) || {};
2476-
const path = serverOptions.prefix || serverOptions.path || "/ws";
2477-
2478-
if (typeof req.url !== "string") {
2479-
return false;
2480-
}
2481-
2482-
const queryIndex = req.url.indexOf("?");
2483-
2484-
return (
2485-
(queryIndex === -1 ? req.url : req.url.slice(0, queryIndex)) === path
2486-
);
2487-
}
2488-
24892458
/**
24902459
* Judge every client that reaches the hot endpoint.
24912460
*
@@ -2540,11 +2509,14 @@ class Server {
25402509
(!this.isValidHost(headers, "origin") ||
25412510
!this.isSameOrigin(headers)))
25422511
) {
2512+
// Said to the client before it is closed, so the reason reaches the
2513+
// page rather than only this server's log — a refusal that closes
2514+
// silently is one the developer has nowhere to read.
25432515
this.sendMessage([client], "error", "Invalid Host/Origin header");
25442516

2545-
// With https enabled, the sendMessage above is encrypted
2546-
// asynchronously so not yet sent. Terminate would prevent it
2547-
// sending, so use close to allow it to be sent.
2517+
// With https enabled, the message above is encrypted asynchronously
2518+
// so not yet sent. Terminate would prevent it sending, so use close
2519+
// to allow it to be sent.
25482520
client.close();
25492521
}
25502522
},
@@ -3197,7 +3169,16 @@ class Server {
31973169
for (const client of clients) {
31983170
// `ws` uses `WebSocket.OPEN`, which is `1`
31993171
if (client.readyState === 1) {
3200-
message ??= JSON.stringify({ type, data, params });
3172+
message ??= JSON.stringify(
3173+
// The runtime reading this is webpack-dev-middleware's, which
3174+
// dispatches on `action`. `error` is the one type this server still
3175+
// sends itself — a refusal it decided on, which no build knows
3176+
// about — so it goes over in the shape that runtime logs rather
3177+
// than as an action it would drop.
3178+
type === "error"
3179+
? { action: "error", message: data }
3180+
: { type, data, params },
3181+
);
32013182
client.send(message);
32023183
}
32033184
}
@@ -3442,27 +3423,14 @@ class Server {
34423423
return;
34433424
}
34443425

3445-
const headers =
3446-
/** @type {{ [key: string]: string | undefined }} */
3447-
(req.headers);
3448-
3449-
// A handshake is not subject to the browser's own origin rules — it
3450-
// sends `Origin` and pays no attention to what comes back — so this is
3451-
// the only place it can be refused.
3452-
if (
3453-
!this.isValidHost(headers, "host") ||
3454-
!this.isValidHost(headers, "origin") ||
3455-
!this.isSameOrigin(headers)
3456-
) {
3457-
// Only a request that was for the hot endpoint is ours to refuse.
3458-
// Anything else belongs to a proxy or to another listener.
3459-
if (this.isHotEndpoint(req)) {
3460-
socket.destroy();
3461-
}
3462-
3463-
return;
3464-
}
3465-
3426+
// Answered rather than refused here, even for a client that will be
3427+
// turned away: the handshake has to complete for the reason to reach
3428+
// the page, which is where someone debugging a dev server reads it.
3429+
// `guardHotEndpoint` closes it immediately afterwards, having said so.
3430+
//
3431+
// The server is not handed over with `attach` even so, because owning
3432+
// this listener is what keeps an upgrade that was never ours — a
3433+
// proxied one — with whoever it belongs to.
34663434
middleware.handleUpgrade(req, socket, head);
34673435
});
34683436

‎lib/hotOptions.js‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,13 @@ export default function hotOptions({
224224
overlay: clientOverlay(client.overlay),
225225
};
226226

227-
if (client.logging !== undefined) {
228-
result.client.logging = client.logging;
229-
}
227+
// The level, plus the name every message is labelled with: this package is
228+
// the one a developer installed, so the console says so rather than naming
229+
// a dependency they would not think to report a problem to.
230+
result.client.logging = {
231+
name: "webpack-dev-server",
232+
...(client.logging === undefined ? {} : { level: client.logging }),
233+
};
230234

231235
if (client.progress !== undefined) {
232236
result.client.progress = client.progress;

0 commit comments

Comments
 (0)