fix: treat loopback aliases as equivalent in isSameOrigin#5659
Open
ThierryRakotomanana wants to merge 1 commit intowebpack:mainfrom
Open
fix: treat loopback aliases as equivalent in isSameOrigin#5659ThierryRakotomanana wants to merge 1 commit intowebpack:mainfrom
ThierryRakotomanana wants to merge 1 commit intowebpack:mainfrom
Conversation
- When host: localhost is configured, the OS decides at bind time whether localhost resolves to 127.0.0.1 (IPv4) or ::1 (IPv6). - This causes isSameOrigin() to reject valid WebSocket connections because the final string comparison localhost === ::1 fails, triggering an infinite reconnection loop in the browser console.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is a follow-up to webpack/webpack-cli#4715, where Alexander pointed out after merging. That PR fixed the webpack-cli templates by removing the hardcoded
host: "localhost"that was causing WebSocket disconnection loops. But the root cause lives deeper, in webpack-dev-server itself.When
host: 'localhost'is configured, the OS decides at bind time whetherlocalhostresolves to127.0.0.1(IPv4) or::1(IPv6). This is OS-dependent and not under the user's control, Windows 10/11 with modern network stacks commonly prioritizes IPv6, linux127.0.0.1During the WebSocket handshake,
createWebSocketServer()callsisSameOrigin()which compares the parsedoriginheader against the parsedhostheader. When the OS resolveslocalhostto::1, the browser sends:The final line of
isSameOrigin()is a strict string comparison:This causes the server to reject the WebSocket connection and send
Invalid Host/Origin headerback to the client, triggering an infinite reconnection loop visible in the browser console:This happens even though
localhost,127.0.0.1, and::1are all loopback addresses and already individually trusted byisValidHost()earlier in the same handshake check.What kind of change does this PR introduce?
a fix when host is set to
localhostDid you add tests for your changes?
Four tests added to
test/e2e/allowed-hosts.test.jsinside the existingdescribe("check host headers")block, following the same directserver.isSameOrigin()call pattern as existingisValidHost()Does this PR introduce a breaking change?
No, and this fix does not widen the trust boundary in any way. Here is the exact reasoning:
All three values are already individually trusted.
isValidHost(), called earlier in the same WebSocket handshake, already passeslocalhost,127.0.0.1, and::1unconditionally. This fix only resolves the inconsistency when comparing two values that are both already trusted.Both sides must be loopback simultaneously. The condition uses
&&, it only passes when bothoriginandhostare in the loopback set. Thehostheader comes from the server's own socket binding. An external attacker cannot control the server'shostheader to be127.0.0.1or::1.Browsers enforce origin strictly. A page served from
https://evil.comwill always sendOrigin: https://evil.com. No browser will sendOrigin: http://localhostfor a cross-origin request from an external site, making spoofing of the loopbackoriginimpossible.If relevant, what needs to be documented once your changes are merged or what have you already documented?
no need
Use of AI
no