Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// ------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Kiota.Abstractions;

Expand Down Expand Up @@ -44,5 +45,12 @@ public int MaxRedirect
/// A boolean value to determine if we redirects are allowed if the scheme changes(e.g. https to http). Defaults to false.
/// </summary>
public bool AllowRedirectOnSchemeChange { get; set; } = false;

/// <summary>
/// A collection of header names that should be removed when the host or scheme changes during a redirect.
/// This is useful for removing sensitive headers like API keys that should not be sent to different hosts.
/// The Authorization and Cookie headers are always removed on host/scheme change regardless of this setting.
/// </summary>
public ICollection<string> SensitiveHeaders { get; set; } = new List<string>();
Comment thread
baywet marked this conversation as resolved.
Outdated
}
}
62 changes: 61 additions & 1 deletion 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 46 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 46 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 46 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 46 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 46 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
if(request == null) throw new ArgumentNullException(nameof(request));

Expand Down Expand Up @@ -119,11 +119,28 @@
newRequest.RequestUri = new Uri(baseAddress + response.Headers.Location);
}

// Remove Auth if http request's scheme or host changes
// Remove Authorization and Cookie header if http request's scheme or host changes
if(!newRequest.RequestUri.Host.Equals(request.RequestUri?.Host) ||
!newRequest.RequestUri.Scheme.Equals(request.RequestUri?.Scheme))
{
newRequest.Headers.Authorization = null;
newRequest.Headers.Remove("Cookie");

// Remove any additional sensitive headers configured in the options
if(redirectOption.SensitiveHeaders.Count > 0)
{
foreach(var header in redirectOption.SensitiveHeaders)
{
newRequest.Headers.Remove(header);
}
}
}

// Remove ProxyAuthorization if no proxy is configured or the URL is bypassed
var proxyResolver = GetProxyResolver();
if(proxyResolver == null || proxyResolver(newRequest.RequestUri) == null)
{
newRequest.Headers.ProxyAuthorization = null;
}

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

private bool ShouldRedirect(HttpResponseMessage responseMessage, RedirectHandlerOption redirectOption)

Check warning on line 180 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 180 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 180 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 180 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 All @@ -183,5 +200,48 @@
};
}

/// <summary>
/// Gets a callback that resolves the proxy URI for a given destination URI.
/// </summary>
/// <returns>A function that takes a destination URI and returns the proxy URI, or null if no proxy is configured or the destination is bypassed.</returns>
private Func<Uri, Uri?>? GetProxyResolver()
Comment thread
MIchaelMainer marked this conversation as resolved.
{
var proxy = GetProxyFromFinalHandler();
if(proxy == null)
return null;
return destination => proxy.IsBypassed(destination) ? null : proxy.GetProxy(destination);
}

/// <summary>
/// Traverses the handler chain to find the final handler and extract its proxy settings.
/// </summary>
/// <returns>The IWebProxy from the final handler, or null if not found.</returns>
private IWebProxy? GetProxyFromFinalHandler()

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

View workflow job for this annotation

GitHub Actions / Build

Make 'GetProxyFromFinalHandler' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)
Comment thread
MIchaelMainer marked this conversation as resolved.
{
#if BROWSER
// Browser platform does not support proxy configuration
return null;
#else
var handler = InnerHandler;
while(handler != null)
{
#if NETFRAMEWORK
if(handler is WinHttpHandler winHttpHandler)
return winHttpHandler.Proxy;
#endif
#if NET5_0_OR_GREATER
if(handler is SocketsHttpHandler socketsHandler)
return socketsHandler.Proxy;
#endif
if(handler is HttpClientHandler httpClientHandler)
return httpClientHandler.Proxy;
if(handler is DelegatingHandler delegatingHandler)
handler = delegatingHandler.InnerHandler;
else
break;
}
return null;
#endif
}
}
}
Loading
Loading