Skip to content

Fix duplicate websocket header handling in IIS Out-Of-Process handler#65077

Open
Copilot wants to merge 9 commits intomainfrom
copilot/fix-duplicate-websocket-header
Open

Fix duplicate websocket header handling in IIS Out-Of-Process handler#65077
Copilot wants to merge 9 commits intomainfrom
copilot/fix-duplicate-websocket-header

Conversation

Copy link
Contributor

Copilot AI commented Jan 15, 2026

Fix duplicate websocket header handling in IIS Out-Of-Process handler

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Fix IIS Out-Of-Process to accept WebSocket requests with duplicate Upgrade: websocket, websocket header

Description

IIS Out-Of-Process rejected WebSocket requests when the Upgrade header contained duplicate values like websocket, websocket, while Kestrel accepted them. The RFC doesn't require values to be distinct.

Root cause: The ANCM check in forwardinghandler.cpp required an exact match:

if (cchHeader == 9 && _stricmp(pszWebSocketHeader, "websocket") == 0)

This fails when header length != 9 (e.g., websocket, websocket is 20 chars).

Fix: Added a generic ContainsToken() helper function that parses comma-separated header values and checks if any token matches the expected value (case-insensitive), matching Kestrel's GetCommaSeparatedValues() approach.

Changes:

  • forwardinghandler.cpp: Add generic ContainsToken() helper function to properly parse comma-separated header values
  • WebSocketMiddlewareTests.cs: Add Kestrel unit test for duplicate websocket header scenario
  • WebSocketTests.cs: Add IIS Out-Of-Process functional test to verify the C++ fix works correctly with duplicate Upgrade: websocket, websocket headers
Original prompt

This section details on the original issue you should resolve

<issue_title>Inconsistency between IIS and Kestrel for websocket connect operation with duplicate 'Upgrade: websocket, websocket' header </issue_title>
<issue_description>### Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Accidentally I've sent a websocket connect request with duplicate websocket header like here:
image

It worked on a Kestrel based app: websocket connection was established, and I could send \ receive messages and gracefully terminate a connection.

  • It failed on IIS hosted app: after calling context.WebSockets.AcceptWebSocketAsync() websocket.State is Open
    image
  • but client immediately disconnected:
Could not connect to ws://localhost:21156
12:00:24
Error: Unexpected server response: 101
Handshake Details
Request URL: http://localhost:21156/
Request Method: GET
Status Code: 101 Switching Protocols
Request Headers
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: gCyKijP/CPAUtW3Cjyf8gA==
Connection: Upgrade
Upgrade: websocket, websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Host: localhost:21156
Response Headers
Transfer-Encoding: chunked
Upgrade: websocket
Server: Microsoft-IIS/10.0
Sec-WebSocket-Accept: fiwTshwZAtNTY2V2+ynOPK4kFNk=
X-Powered-By: ASP.NET
Date: Mon, 28 Aug 2023 10:00:24 GMT

Expected Behavior

As per my understanding, either Kestrel implementation should fail as well, or IIS should handle duplicate Upgrade: websocket, websocket header. Let me know, if I am doing something wrong.

Steps To Reproduce

repro is as simple as creating an ASP.NET core project and using such setup in Program.cs:

app.UseWebSockets();
app.Use(async (context, next) =>
{
    if (context.WebSockets.IsWebSocketRequest)
    {
        using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
        await Echo(webSocket);
    }
    else
    {
        await next(context);
    }
});

app.Run();

and make a websocket connect call with Upgrade: websocket, websocket header.

if you need a repro in a repository:

Exceptions (if any)

Unhandled exception. System.Net.WebSockets.WebSocketException (203): The server's response was missing the required header 'Connection'.
   at System.Net.WebSockets.WebSocketHandle.ValidateHeader(HttpHeaders headers, String name, String expectedValue)
   at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
   at System.Net.WebSockets.ClientWebSocket.ConnectAsyncCore(Uri uri, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in C:\code\personal\repro-1\WebApp\Client\Program.cs:line 10
   at Program.<Main>(String[] args)

.NET Version

net6.0 (local 6.0.316)

Anything else?

No response</issue_description>

<agent_instructions>IIS should not fail the Upgrade: websocket, websocket case, and be less restrictive - reproducing the same behavior as Kestrel does</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@amcasey I don't see anything in the [RFC](https://www.rfc-editor.org/rfc/rfc9110#field.upgrade) about the values needing to be distinct, so it does seem like IIS is being unnecessarily restrictive.

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 4 commits January 15, 2026 16:40
Co-authored-by: DeagleGross <31598696+DeagleGross@users.noreply.github.com>
Co-authored-by: DeagleGross <31598696+DeagleGross@users.noreply.github.com>
Co-authored-by: DeagleGross <31598696+DeagleGross@users.noreply.github.com>
Co-authored-by: DeagleGross <31598696+DeagleGross@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix inconsistency between IIS and Kestrel for websocket connection Fix duplicate websocket header handling in IIS Out-Of-Process handler Jan 15, 2026
Copilot AI requested a review from DeagleGross January 15, 2026 16:46
@DeagleGross
Copy link
Member

@copilot updated the function to be generic one, review the code and apply suggestions if any

…OKEN_LENGTH constant

Co-authored-by: DeagleGross <31598696+DeagleGross@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 15, 2026

@copilot updated the function to be generic one, review the code and apply suggestions if any

Reviewed the generic ContainsToken function - it looks good! I made a couple of minor cleanups in commit 8111dab:

  1. Updated the comment on line 67 from "websocket" to "expected token" since the function is now generic
  2. Changed the call site to use the WEBSOCKET_TOKEN_LENGTH constant instead of the inline magic number 9

@DeagleGross DeagleGross marked this pull request as ready for review January 15, 2026 19:38
Copilot AI review requested due to automatic review settings January 15, 2026 19:38
@DeagleGross DeagleGross requested a review from JamesNK as a code owner January 15, 2026 19:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an inconsistency between IIS Out-Of-Process and Kestrel when handling WebSocket upgrade requests with duplicate Upgrade: websocket, websocket headers. Previously, IIS Out-Of-Process rejected such requests due to an exact string match check, while Kestrel accepted them by parsing comma-separated header values. The RFC doesn't require header values to be distinct.

Changes:

  • Added a generic ContainsToken() helper function in the ANCM forwarder to properly parse and validate comma-separated HTTP header values
  • Added a test demonstrating that Kestrel correctly accepts WebSocket upgrade requests with duplicate "websocket" header values

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp Implements ContainsToken() helper function to parse comma-separated header values and replaces exact string match with token-based matching for WebSocket upgrade detection
src/Middleware/WebSockets/test/UnitTests/WebSocketMiddlewareTests.cs Adds test verifying that duplicate websocket upgrade headers are accepted

Co-authored-by: DeagleGross <31598696+DeagleGross@users.noreply.github.com>
@dotnet-policy-service
Copy link
Contributor

Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime.
To make sure no conflicting changes have occurred, please rerun validation before merging. You can do this by leaving an /azp run comment here (requires commit rights), or by simply closing and reopening.

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Jan 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistency between IIS and Kestrel for websocket connect operation with duplicate 'Upgrade: websocket, websocket' header

2 participants