Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e6a2654
fix: remove proxy and cookie auth headers on insecure redirect
MIchaelMainer Feb 11, 2026
fa48454
fix: dispose of HttpRequestMessage instance
MIchaelMainer Feb 11, 2026
a883463
test: update tests to dispose HttpRequestMessage
MIchaelMainer Feb 11, 2026
652569a
Merge branch 'main' into mmainer/redirect-sec
ramsessanchez Feb 13, 2026
088f69f
fix<security): remove ProxyAuthorization header if the new URL doesn'…
MIchaelMainer Feb 19, 2026
7e8e746
fix(security): enable configuration to allow removing arbitrary heade…
MIchaelMainer Feb 19, 2026
9ca26d6
test(redirecthandlertests): use a more concise statement
MIchaelMainer Feb 19, 2026
c066d52
test: fix using directive order
MIchaelMainer Feb 19, 2026
ee6e404
Merge branch 'mmainer/redirect-sec' of https://github.com/microsoft/k…
MIchaelMainer Feb 19, 2026
7a89dc0
Merge branch 'main' into mmainer/redirect-sec
adrian05-ms Feb 24, 2026
0bba6b4
Fixing linter errors
adrian05-ms Feb 24, 2026
6de56eb
Testing fix for build error
adrian05-ms Feb 24, 2026
6336c5a
Merge branch 'main' into mmainer/redirect-sec
adrian05-ms Feb 25, 2026
075bb15
code review changes
adrian05-ms Feb 25, 2026
399e49c
linting: use where when possible
baywet Feb 25, 2026
c1ea4bc
changing approach on removing sensitive headers
adrian05-ms Feb 25, 2026
588b9c6
Fixing format and test coverage
adrian05-ms Feb 25, 2026
afaadca
sonar code suggestions
adrian05-ms Feb 25, 2026
9677a7e
fix(security): add customizable callback to allow removing headers on…
MIchaelMainer Feb 26, 2026
746a846
fix(security): add customizable callback to remove headers on redirect
MIchaelMainer Feb 26, 2026
1f7b2cb
test: validate removing headers on redirect
MIchaelMainer Feb 26, 2026
cb728ed
chore: fix whitespace
MIchaelMainer Feb 26, 2026
c16b97f
chore: make web proxy static
MIchaelMainer Feb 27, 2026
21755df
chore: make proxy resolver static
MIchaelMainer Feb 27, 2026
b70eb92
Update src/http/httpClient/Middleware/RedirectHandler.cs
adrian05-ms Feb 27, 2026
c845bdd
reverting back static change
adrian05-ms Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"Kiota"
],
"editor.formatOnSave": true,
"dotnet-test-explorer.testProjectPath": "**/*.Tests.csproj"
"dotnet-test-explorer.testProjectPath": "**/*.Tests.csproj",
"sarif-viewer.connectToGithubCodeScanning": "on"
}
2 changes: 2 additions & 0 deletions src/http/httpClient/Middleware/RedirectHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
/// <param name="request">The <see cref="HttpRequestMessage"/> to send.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>for the request.</param>
/// <returns>The <see cref="HttpResponseMessage"/>.</returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

Check warning on line 44 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 33 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 44 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 33 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 44 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 33 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 44 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 33 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 44 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 33 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
if(request == null) throw new ArgumentNullException(nameof(request));

Expand Down Expand Up @@ -124,6 +124,8 @@
!newRequest.RequestUri.Scheme.Equals(request.RequestUri?.Scheme))
{
newRequest.Headers.Authorization = null;
newRequest.Headers.ProxyAuthorization = null;
Comment thread
baywet marked this conversation as resolved.
Outdated
newRequest.Headers.Remove("Cookie");
}

// If scheme has changed. Ensure that this has been opted in for security reasons
Expand Down Expand Up @@ -160,7 +162,7 @@
}
}

private bool ShouldRedirect(HttpResponseMessage responseMessage, RedirectHandlerOption redirectOption)

Check warning on line 165 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'ShouldRedirect' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)

Check warning on line 165 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'ShouldRedirect' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)

Check warning on line 165 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'ShouldRedirect' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)

Check warning on line 165 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'ShouldRedirect' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)

Check warning on line 165 in src/http/httpClient/Middleware/RedirectHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'ShouldRedirect' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)
{
return IsRedirect(responseMessage.StatusCode) && redirectOption.ShouldRedirect(responseMessage) && redirectOption.MaxRedirect > 0;
}
Expand Down
120 changes: 120 additions & 0 deletions tests/http/httpClient/Middleware/RedirectHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,125 @@ public async Task ExceedMaxRedirectsShouldThrowsException()
Assert.Equal("Max redirects exceeded. Redirect count : 5", exception.InnerException?.Message);
Assert.IsType<InvalidOperationException>(exception);
}

[Theory]
[InlineData(HttpStatusCode.MovedPermanently)] // 301
[InlineData(HttpStatusCode.Found)] // 302
[InlineData(HttpStatusCode.TemporaryRedirect)] // 307
[InlineData((HttpStatusCode)308)] // 308
public async Task RedirectWithDifferentHostShouldRemoveProxyAuthHeader(HttpStatusCode statusCode)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://example.org/foo");
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
httpRequestMessage.Headers.ProxyAuthorization = new AuthenticationHeaderValue("fooAuth", "aparam");
var redirectResponse = new HttpResponseMessage(statusCode);
redirectResponse.Headers.Location = new Uri("http://example.net/bar");
this._testHttpMessageHandler.SetHttpResponse(redirectResponse, new HttpResponseMessage(HttpStatusCode.OK));// sets the mock response
// Act
var response = await _invoker.SendAsync(httpRequestMessage, new CancellationToken());
// Assert
Assert.NotSame(response.RequestMessage, httpRequestMessage);
Assert.NotSame(response.RequestMessage?.RequestUri?.Host, httpRequestMessage.RequestUri?.Host);
Assert.Null(response.RequestMessage?.Headers.ProxyAuthorization);
}

[Theory]
[InlineData(HttpStatusCode.MovedPermanently)] // 301
[InlineData(HttpStatusCode.Found)] // 302
[InlineData(HttpStatusCode.TemporaryRedirect)] // 307
[InlineData((HttpStatusCode)308)] // 308
public async Task RedirectWithDifferentHostShouldRemoveCookieHeader(HttpStatusCode statusCode)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://example.org/foo");
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
httpRequestMessage.Headers.Add("Cookie", "session=abc123");
var redirectResponse = new HttpResponseMessage(statusCode);
redirectResponse.Headers.Location = new Uri("http://example.net/bar");
this._testHttpMessageHandler.SetHttpResponse(redirectResponse, new HttpResponseMessage(HttpStatusCode.OK));// sets the mock response
// Act
var response = await _invoker.SendAsync(httpRequestMessage, new CancellationToken());
// Assert
Assert.NotSame(response.RequestMessage, httpRequestMessage);
Assert.NotSame(response.RequestMessage?.RequestUri?.Host, httpRequestMessage.RequestUri?.Host);
Assert.False(response.RequestMessage?.Headers.Contains("Cookie"));
}

[Theory]
[InlineData(HttpStatusCode.MovedPermanently)] // 301
[InlineData(HttpStatusCode.Found)] // 302
[InlineData(HttpStatusCode.TemporaryRedirect)] // 307
[InlineData((HttpStatusCode)308)] // 308
public async Task RedirectWithDifferentSchemeShouldRemoveProxyAuthHeaderIfAllowRedirectOnSchemeChangeIsEnabled(HttpStatusCode statusCode)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.org/foo");
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
httpRequestMessage.Headers.ProxyAuthorization = new AuthenticationHeaderValue("fooAuth", "aparam");
var redirectResponse = new HttpResponseMessage(statusCode);
redirectResponse.Headers.Location = new Uri("http://example.org/bar");
this._redirectHandler.RedirectOption.AllowRedirectOnSchemeChange = true;// Enable redirects on scheme change
this._testHttpMessageHandler.SetHttpResponse(redirectResponse, new HttpResponseMessage(HttpStatusCode.OK));// sets the mock response
// Act
var response = await _invoker.SendAsync(httpRequestMessage, new CancellationToken());
// Assert
Assert.NotSame(response.RequestMessage, httpRequestMessage);
Assert.NotSame(response.RequestMessage?.RequestUri?.Scheme, httpRequestMessage.RequestUri?.Scheme);
Assert.Null(response.RequestMessage?.Headers.ProxyAuthorization);
}

[Theory]
[InlineData(HttpStatusCode.MovedPermanently)] // 301
[InlineData(HttpStatusCode.Found)] // 302
[InlineData(HttpStatusCode.TemporaryRedirect)] // 307
[InlineData((HttpStatusCode)308)] // 308
public async Task RedirectWithDifferentSchemeShouldRemoveCookieHeaderIfAllowRedirectOnSchemeChangeIsEnabled(HttpStatusCode statusCode)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.org/foo");
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
httpRequestMessage.Headers.Add("Cookie", "session=abc123");
var redirectResponse = new HttpResponseMessage(statusCode);
redirectResponse.Headers.Location = new Uri("http://example.org/bar");
this._redirectHandler.RedirectOption.AllowRedirectOnSchemeChange = true;// Enable redirects on scheme change
this._testHttpMessageHandler.SetHttpResponse(redirectResponse, new HttpResponseMessage(HttpStatusCode.OK));// sets the mock response
// Act
var response = await _invoker.SendAsync(httpRequestMessage, new CancellationToken());
// Assert
Assert.NotSame(response.RequestMessage, httpRequestMessage);
Assert.NotSame(response.RequestMessage?.RequestUri?.Scheme, httpRequestMessage.RequestUri?.Scheme);
Assert.False(response.RequestMessage?.Headers.Contains("Cookie"));
}

[Fact]
public async Task RedirectWithSameHostShouldKeepProxyAuthHeader()
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://example.org/foo");
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
httpRequestMessage.Headers.ProxyAuthorization = new AuthenticationHeaderValue("fooAuth", "aparam");
var redirectResponse = new HttpResponseMessage(HttpStatusCode.Redirect);
redirectResponse.Headers.Location = new Uri("http://example.org/bar");
this._testHttpMessageHandler.SetHttpResponse(redirectResponse, new HttpResponseMessage(HttpStatusCode.OK));// sets the mock response
// Act
var response = await _invoker.SendAsync(httpRequestMessage, new CancellationToken());
// Assert
Assert.NotSame(response.RequestMessage, httpRequestMessage);
Assert.Equal(response.RequestMessage?.RequestUri?.Host, httpRequestMessage.RequestUri?.Host);
Assert.NotNull(response.RequestMessage?.Headers.ProxyAuthorization);
}

[Fact]
public async Task RedirectWithSameHostShouldKeepCookieHeader()
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://example.org/foo");
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
httpRequestMessage.Headers.Add("Cookie", "session=abc123");
var redirectResponse = new HttpResponseMessage(HttpStatusCode.Redirect);
redirectResponse.Headers.Location = new Uri("http://example.org/bar");
this._testHttpMessageHandler.SetHttpResponse(redirectResponse, new HttpResponseMessage(HttpStatusCode.OK));// sets the mock response
// Act
var response = await _invoker.SendAsync(httpRequestMessage, new CancellationToken());
// Assert
Assert.NotSame(response.RequestMessage, httpRequestMessage);
Assert.Equal(response.RequestMessage?.RequestUri?.Host, httpRequestMessage.RequestUri?.Host);
Assert.True(response.RequestMessage?.Headers.Contains("Cookie"));
}
}
}
Loading