CVE-2026-9595: Exact HMR WebSocket Path Matching Fix in webpack-dev-server
Summary
The patch changes webpack-dev-server's upgrade dispatch so HMR WebSocket requests are identified using the same raw path comparison semantics as the underlying ws server, preventing proxy middleware from intercepting URL variants that ws itself would reject as HMR. This addresses the parser discrepancy described in the CVE and is reinforced by regression tests covering exact-match and variant paths.
Analysis
Vulnerability
CVE-2026-9595 describes a WebSocket proxying flaw in webpack-dev-server where HMR upgrade dispatch used URL parsing and normalization that did not match the underlying ws server's request-target handling. Per the patch comments and tests in the upstream fix, webpack-dev-server previously derived pathname via new URL(req.url, "http://0.0.0.0"), while ws performs a raw, case-sensitive comparison with only the query string stripped. That discrepancy allowed path variants such as //ws, /WS, or percent-encoded forms to be classified by dev-server as HMR traffic even though ws would not accept them as the HMR socket. As a result, upgrade handling could fall through into user-configured proxy middleware, enabling interception of local HMR WebSocket traffic and bypassing Host/Origin protections, with cookie leakage to proxy targets as noted in the CVE record and related references.
The issue is documented in the upstream commit 948d5e6089bebcd801dac2cbe3ed4f80b64f117a, the associated pull request #4316, the NVD entry, and the CVE record.
if (hmrPath && req.url) {
const { pathname } = new URL(req.url, "http://0.0.0.0");
if (pathname === hmrPath) {
return;
}
}
proxyUpgrade(req, socket, head);Patch
The patch updates lib/Server.js so HMR path detection mirrors ws semantics instead of Node URL normalization. It first extracts the configured HMR path from this.options.webSocketServer.options.path, then wraps proxy upgrade handling with a guard that compares the raw request target exactly, stripping only the query string. The patch comment explicitly states the intent: match WebSocketServer#shouldHandle behavior in ws and avoid normalization that would incorrectly classify variants as HMR.
if (hmrPath && typeof req.url === "string") {
const queryIndex = req.url.indexOf("?");
const pathname =
queryIndex !== -1 ? req.url.slice(0, queryIndex) : req.url;
if (pathname === hmrPath) {
return;
}
}
proxyUpgrade(req, socket, head);The test suite additions in the official patch and PR #4316 are substantial and directly target the bug mechanics:
- Regression coverage verifies that the real HMR path is served locally and not forwarded to a permissive backend proxy.
- Non-HMR upgrade paths are still forwarded to the user proxy, preserving intended behavior.
ws-specific exact-match tests assert that/wsand/ws?token=1are local HMR, while//ws,/ws/,/WS,/wS, and/%77%73are forwarded.- Tests also cover custom configured HMR paths rather than assuming a hardcoded
/ws.
Review
Pros
- The fix addresses the actual parser mismatch rather than adding another validation layer. Aligning dispatch logic with
wsremoves the ambiguity at the source. - The implementation is minimal and low-risk: it replaces URL normalization with a raw string split on
?, which is exactly the behavior described in the patch comment. - The new tests are strong regression guards because they exercise both security-relevant variants and expected proxy pass-through behavior.
- The patch preserves compatibility for legitimate HMR connections, including query strings and custom HMR paths.
- The code comment documents the dependency on
wssemantics, which is important for future maintainers and reduces the chance of reintroducing normalization bugs.
Cons
- The correctness of the fix is intentionally coupled to current
wspath-handling behavior. If upstreamwssemantics change, webpack-dev-server may need to be updated again. - The patch is narrowly scoped to HMR upgrade dispatch. It does not establish a broader invariant or shared helper for path matching across all WebSocket-related code paths.
- The available diff does not show additional hardening around Host/Origin checks themselves; it prevents the bypass by ensuring the request is routed correctly before proxying.
Verdict
Root-cause.
This patch fixes the root cause identified in the advisory: inconsistent interpretation of the WebSocket request target between webpack-dev-server and ws. By making HMR path classification use the same raw, case-sensitive, query-stripped comparison as the underlying WebSocket server, it closes the dispatch gap that allowed proxy interception of local HMR traffic. The regression tests are directly aligned with the exploit shape described in NVD and the upstream patch discussion in PR #4316. For engineering teams, this looks like a well-scoped and technically correct remediation rather than a superficial filter.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- CORS.js
This JavaScript hands-on lab is the closest defensive match to CVE-2026-9595 because the underlying issue is an Origin/Host trust failure during WebSocket proxying. Practicing reflected Origin validation flaws helps build the exact habit needed here: never trust raw header values or partial string checks when deciding whether a cross-origin request or upgraded connection is allowed.
- CORS.js
This variant focuses on wildcard Origin acceptance, which is highly relevant to the CVE’s Host/Origin validation bypass theme. It is useful for learning how overly broad trust decisions can expose cookies or authenticated browser traffic to unintended targets, similar to what can happen when proxied WebSocket requests are insufficiently constrained.
- CORS.js
This lab covers allowing a null Origin, another practical case of weak origin policy enforcement. While not WebSocket-specific, it reinforces the same defensive review pattern needed for this patch: normalize/parses inputs consistently, validate allowed origins strictly, and avoid bypasses caused by edge-case header or URL parsing discrepancies.