close
Skip to content

Securely overwrite X-Content-Length for mitigating request smuggling via TransferEncoding & ContentLength headers#65445

Merged
DeagleGross merged 1 commit into
dotnet:mainfrom
DeagleGross:dmkorolev/x-content-length-validation
Feb 17, 2026
Merged

Securely overwrite X-Content-Length for mitigating request smuggling via TransferEncoding & ContentLength headers#65445
DeagleGross merged 1 commit into
dotnet:mainfrom
DeagleGross:dmkorolev/x-content-length-validation

Conversation

@DeagleGross
Copy link
Copy Markdown
Member

Noticed that we are removing Content-Length header and writing the value to X-Content-Length in case of detecting request smuggling attempt via Transfer-Encoding and Content-Length headers.

However, if user has explicitly passed X-Content-Length, code will throw because we are doing a standard dictionary.Add() of X-Content-Length.

Changed to use TryAdd() to avoid unintended behavior.

@DeagleGross DeagleGross self-assigned this Feb 16, 2026
@DeagleGross DeagleGross added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Feb 16, 2026
Copilot AI review requested due to automatic review settings February 16, 2026 10:08
@DeagleGross DeagleGross enabled auto-merge (squash) February 16, 2026 10:10
Copy link
Copy Markdown
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 pull request addresses a potential exception in request smuggling mitigation logic across three ASP.NET Core server implementations (Kestrel, IIS, and HttpSys). When both Transfer-Encoding and Content-Length headers are present in a request (a potential request smuggling attack), the servers move the Content-Length value to an X-Content-Length header for diagnostic purposes. The PR changes from using Add() to TryAdd() to prevent exceptions when a client has explicitly sent an X-Content-Length header.

Changes:

  • Modified header handling logic to use TryAdd() instead of Add() for X-Content-Length across Kestrel, IIS, and HttpSys server implementations
  • Added tests for Kestrel and HttpSys to verify behavior when all three headers (Transfer-Encoding, Content-Length, and X-Content-Length) are present

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs Changed to use TryAdd for X-Content-Length to avoid exception, added comment explaining behavior
src/Servers/IIS/IIS/src/Core/IISHttpContext.cs Changed to use TryAdd for X-Content-Length to avoid exception, added comment explaining behavior
src/Servers/HttpSys/src/RequestProcessing/Request.cs Changed to use TryAdd for X-Content-Length to avoid exception, added comment explaining behavior
src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs Added test verifying X-Content-Length is preserved when explicitly set by user
src/Servers/HttpSys/test/FunctionalTests/RequestHeaderTests.cs Added test verifying X-Content-Length is preserved when explicitly set by user

Comment on lines +197 to +198
Assert.Single(requestHeaders["X-Content-Length"]);
Assert.Equal("123", requestHeaders["X-Content-Length"]);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

Test behavior concern: This test verifies that when a user explicitly provides X-Content-Length, it's preserved instead of being overwritten with the actual Content-Length. However, this behavior might not align with the security purpose of X-Content-Length, which is to preserve the actual Content-Length for diagnostic purposes during request smuggling detection. If the implementation changes to always overwrite X-Content-Length with the server-observed Content-Length (which would be more secure), this test's assertions would need to be updated accordingly.

Suggested change
Assert.Single(requestHeaders["X-Content-Length"]);
Assert.Equal("123", requestHeaders["X-Content-Length"]);
Assert.True(requestHeaders.ContainsKey("X-Content-Length"));
Assert.Single(requestHeaders["X-Content-Length"]);

Copilot uses AI. Check for mistakes.
IHeaderDictionary headerDictionary = Headers;
headerDictionary.Add("X-Content-Length", headerDictionary[HeaderNames.ContentLength]);

// dont overwrite if user explicitly set X-Content-Length
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

Grammar issue: "dont" should be "don't". This comment should match the style used in the other implementations (Http1MessageBody.cs and IISHttpContext.cs).

Suggested change
// dont overwrite if user explicitly set X-Content-Length
// Don't overwrite if user explicitly set X-Content-Length

Copilot uses AI. Check for mistakes.
Comment on lines +186 to +187
// if user already passed X-Content-Length, we won't overwrite it
_ = headerDictionary.TryAdd("X-Content-Length", headerDictionary[HeaderNames.ContentLength]);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

Security concern: Using TryAdd preserves a user-supplied X-Content-Length header instead of recording the actual Content-Length value that triggered the smuggling detection. This could allow an attacker to hide the true Content-Length value in diagnostics/logs.

Consider using a pattern that always overwrites X-Content-Length with the actual Content-Length value to ensure accurate diagnostics of potential request smuggling attempts. For example:

  • Remove existing X-Content-Length if present
  • Add the actual Content-Length to X-Content-Length
  • Then remove Content-Length

The purpose of X-Content-Length is to preserve the original Content-Length for diagnostic purposes when request smuggling is detected. Preserving an attacker-supplied value defeats this purpose.

Suggested change
// if user already passed X-Content-Length, we won't overwrite it
_ = headerDictionary.TryAdd("X-Content-Length", headerDictionary[HeaderNames.ContentLength]);
headerDictionary.Remove("X-Content-Length");
headerDictionary["X-Content-Length"] = headerDictionary[HeaderNames.ContentLength];

Copilot uses AI. Check for mistakes.
Comment on lines +392 to +393
// if user already passed X-Content-Length, we won't overwrite it
_ = RequestHeaders.TryAdd("X-Content-Length", RequestHeaders[HeaderNames.ContentLength]);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

Security concern: Using TryAdd preserves a user-supplied X-Content-Length header instead of recording the actual Content-Length value that triggered the smuggling detection. This could allow an attacker to hide the true Content-Length value in diagnostics/logs.

Consider using a pattern that always overwrites X-Content-Length with the actual Content-Length value to ensure accurate diagnostics of potential request smuggling attempts. For example:

  • Remove existing X-Content-Length if present
  • Add the actual Content-Length to X-Content-Length
  • Then remove Content-Length

The purpose of X-Content-Length is to preserve the original Content-Length for diagnostic purposes when request smuggling is detected. Preserving an attacker-supplied value defeats this purpose.

Suggested change
// if user already passed X-Content-Length, we won't overwrite it
_ = RequestHeaders.TryAdd("X-Content-Length", RequestHeaders[HeaderNames.ContentLength]);
var originalContentLength = RequestHeaders[HeaderNames.ContentLength];
RequestHeaders.Remove("X-Content-Length");
RequestHeaders["X-Content-Length"] = originalContentLength;

Copilot uses AI. Check for mistakes.
Comment on lines +532 to +533
// dont overwrite if user explicitly set X-Content-Length
_ = headerDictionary.TryAdd("X-Content-Length", headerDictionary[HeaderNames.ContentLength]);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

Security concern: Using TryAdd preserves a user-supplied X-Content-Length header instead of recording the actual Content-Length value that triggered the smuggling detection. This could allow an attacker to hide the true Content-Length value in diagnostics/logs.

Consider using a pattern that always overwrites X-Content-Length with the actual Content-Length value to ensure accurate diagnostics of potential request smuggling attempts. For example:

  • Remove existing X-Content-Length if present
  • Add the actual Content-Length to X-Content-Length
  • Then remove Content-Length

The purpose of X-Content-Length is to preserve the original Content-Length for diagnostic purposes when request smuggling is detected. Preserving an attacker-supplied value defeats this purpose.

Suggested change
// dont overwrite if user explicitly set X-Content-Length
_ = headerDictionary.TryAdd("X-Content-Length", headerDictionary[HeaderNames.ContentLength]);
// Always record the original Content-Length in X-Content-Length for diagnostics,
// even if the client supplied its own X-Content-Length header.
headerDictionary.Remove("X-Content-Length");
headerDictionary["X-Content-Length"] = headerDictionary[HeaderNames.ContentLength];

Copilot uses AI. Check for mistakes.
Comment on lines +392 to +393
// if user already passed X-Content-Length, we won't overwrite it
_ = RequestHeaders.TryAdd("X-Content-Length", RequestHeaders[HeaderNames.ContentLength]);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

Test coverage: The IIS implementation in IISHttpContext.cs was updated with the same logic change (TryAdd for X-Content-Length), but no corresponding test was added for the IIS server. The Kestrel and HttpSys servers both have tests covering the scenario where Transfer-Encoding, Content-Length, and X-Content-Length are all present. Consider adding a similar test for IIS to ensure consistent behavior across all server implementations.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Comment on lines +1046 to +1051
Assert.True(_http1Connection.RequestHeaders.ContainsKey("X-Content-Length"));
Assert.Equal(userXContentLength, _http1Connection.RequestHeaders["X-Content-Length"]);
Assert.True(_http1Connection.RequestHeaders.ContainsKey(HeaderNames.TransferEncoding));
Assert.Equal("chunked", _http1Connection.RequestHeaders[HeaderNames.TransferEncoding]);
Assert.False(_http1Connection.RequestHeaders.ContainsKey(HeaderNames.ContentLength));
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

Test behavior concern: This test verifies that when a user explicitly provides X-Content-Length, it's preserved instead of being overwritten with the actual Content-Length. However, this behavior might not align with the security purpose of X-Content-Length, which is to preserve the actual Content-Length for diagnostic purposes during request smuggling detection. If the implementation changes to always overwrite X-Content-Length with the server-observed Content-Length (which would be more secure), this test's assertions would need to be updated accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +392 to +393
// if user already passed X-Content-Length, we won't overwrite it
_ = RequestHeaders.TryAdd("X-Content-Length", RequestHeaders[HeaderNames.ContentLength]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@DeagleGross DeagleGross merged commit af25746 into dotnet:main Feb 17, 2026
33 checks passed
@dotnet-policy-service dotnet-policy-service Bot added this to the 11.0-preview2 milestone Feb 17, 2026
@DeagleGross DeagleGross deleted the dmkorolev/x-content-length-validation branch February 18, 2026 10:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants