Skip to content

Conversation

@ptarjan
Copy link

@ptarjan ptarjan commented Jan 18, 2026

Fixes: #123363

Many proxies require the Proxy-Authorization header on the first request rather than sending a 407 Proxy Authentication Required response. This is especially common for:

  1. Proxies that drop/ignore unauthenticated connections
  2. HTTPS CONNECT tunnels where connection reuse isn't possible
  3. Environment variable proxies (HTTP_PROXY/HTTPS_PROXY) with embedded credentials (http://user:password@proxy:port)

This change modifies SendWithProxyAuthAsync to proactively send Basic authentication when credentials are available from the ICredentials provider, rather than waiting for a 407 challenge that may never come.

The reactive authentication flow is still preserved for cases where the proxy does send a 407 response with different auth scheme requirements (Digest, NTLM, Negotiate).

Many proxies require the Proxy-Authorization header on the first
request rather than sending a 407 Proxy Authentication Required
response. This is especially common for:

1. Proxies that drop/ignore unauthenticated connections
2. HTTPS CONNECT tunnels where connection reuse isn't possible
3. Environment variable proxies (HTTP_PROXY/HTTPS_PROXY) with
   embedded credentials (http://user:password@proxy:port)

This change modifies SendWithProxyAuthAsync to proactively send
Basic authentication when credentials are available from the
ICredentials provider, rather than waiting for a 407 challenge
that may never come.

The reactive authentication flow is still preserved for cases
where the proxy does send a 407 response with different auth
scheme requirements (Digest, NTLM, Negotiate).
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 18, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Add functional tests to verify that Basic proxy authentication is sent
proactively when credentials are provided, without waiting for a 407
challenge. This tests the fix for proxies that don't send 407 responses.

Tests cover:
- Proactive auth on first HTTP request
- Proactive auth for HTTPS CONNECT tunnels
- DefaultNetworkCredentials are NOT sent proactively (NTLM/Negotiate only)
@ptarjan
Copy link
Author

ptarjan commented Jan 18, 2026

@dotnet-policy-service agree

@ManickaP
Copy link
Member

@ptarjan could you please start with filing an issue to kick off a discussion?

There are security implications of pro-actively sending credentials, especially with basic auth (equivalent of plain text) on non-secured connections. So this is not so straightforward and we cannot just accept a PR without any considerations.

@ManickaP
Copy link
Member

cc @wfurt

@MihaZupan
Copy link
Member

This seems like something that could be driven by the existing SocketsHttpHandler.PreAuthenticate (which is opt-in).

@wfurt
Copy link
Member

wfurt commented Jan 19, 2026

This change modifies SendWithProxyAuthAsync to proactively send Basic authentication when credentials are available from the ICredentials provider, rather than waiting for a 407 challenge that may never come.

This is problematic IMHO and we will have difficulty with security group IMHO as it especially with basic AUTH leaks the credentials. The "Many proxies" claim seems doubtful as it was generally ok for everyone and the 407 is described in RFC.

I also do not understand the case 2 & 3. I'm not sure how for example environment configuration is related to lack of 407.

having said that I would probably be OK with that if that has some opt-in method. Cutting the rounds can be viewed as general optimization. (as for example noted in #33964)

@ptarjan
Copy link
Author

ptarjan commented Jan 19, 2026

@ptarjan could you please start with filing an issue to kick off a discussion?

#123363

There are security implications of pro-actively sending credentials, especially with basic auth (equivalent of plain text) on non-secured connections. So this is not so straightforward and we cannot just accept a PR without any considerations.

This change modifies SendWithProxyAuthAsync to proactively send Basic authentication when credentials are available from the ICredentials provider, rather than waiting for a 407 challenge that may never come.

This is problematic IMHO and we will have difficulty with security group IMHO as it especially with basic AUTH leaks the credentials. The "Many proxies" claim seems doubtful as it was generally ok for everyone and the 407 is described in RFC.

I also do not understand the case 2 & 3. I'm not sure how for example environment configuration is related to lack of 407.

having said that I would probably be OK with that if that has some opt-in method. Cutting the rounds can be viewed as general optimization. (as for example noted in #33964)

Thanks for the detailed feedback. Let me address each point:

On "many proxies" and RFC compliance:

You're right that RFC 7235 specifies the 407 challenge-response flow. However, the real-world issue is that some proxies (particularly in containerized/cloud environments) don't follow the RFC:

  • Case 2 (drop/timeout): Some security-focused proxies silently drop unauthenticated connections rather than responding, to avoid revealing proxy existence to unauthorized clients.
  • Case 3 (connection close after 407): For CONNECT tunnels, some proxies close the TCP connection after sending 407, making retry impossible on the same connection. The client would need to open a new connection, but HttpClient's connection pooling makes this complex.

The specific environment where this was reproduced (http://claude.ai/code) uses an egress proxy that returns 401 Unauthorized (not 407) for unauthenticated CONNECT requests. This is non-compliant but real.

On the opt-in approach:

I don't really understand the credential leak, but I'm eager to learn. If the proxy would really like to see your credentials it could return an HTTP 407 and then you will happily send them along, right? Or is this less about an attacker and instead trying to limit logging? If so, then why is the user even using credentials in their proxy url.

I agree an opt-in is more conservative but it does provide user friction.

Address PR feedback by making proactive proxy authentication opt-in
rather than enabled by default. This preserves RFC 7235 compliant
behavior (waiting for 407 challenge) as the default.

Users can enable proactive auth via:
- AppContext switch: System.Net.Http.EnableProactiveProxyAuth
- Environment variable: DOTNET_SYSTEM_NET_HTTP_ENABLEPROACTIVEPROXYAUTH=1

This is useful for proxies that don't send 407 challenges but instead
drop or reject unauthenticated connections (especially HTTPS CONNECT
tunnel proxies).

Updated tests to use RemoteExecutor with the environment variable
to verify both opt-in and default behavior.
@wfurt
Copy link
Member

wfurt commented Jan 19, 2026

any concern here @dotnet/ncl ? Since this would be opt-in I feel it is ..... acceptable. And it would fix #100515 as well. For the 2 cases we have seen so far the AppContext seems sufficient e.g. adding new explicit API would be overkill and it would still depend on 3rd party tools to use it.

Copy link

@Mondjoe Mondjoe left a comment

Choose a reason for hiding this comment

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

Restore

@karelz
Copy link
Member

karelz commented Jan 27, 2026

Given that there are design decisions concerns, I would suggest to close this PR or switch it to Draft, until we have consensus.
@ptarjan can you please do one of those actions? Thanks!

@ManickaP
Copy link
Member

No need to change into draft, we will take this change. Me and @wfurt will review this.

@ManickaP ManickaP requested review from ManickaP and wfurt January 27, 2026 16:46
Comment on lines +20 to +50
private const string EnableProactiveProxyAuthCtxSwitch = "System.Net.Http.EnableProactiveProxyAuth";
private const string EnableProactiveProxyAuthEnvironmentVariable = "DOTNET_SYSTEM_NET_HTTP_ENABLEPROACTIVEPROXYAUTH";

private static volatile int s_enableProactiveProxyAuth = -1;

private static bool EnableProactiveProxyAuth
{
get
{
int enableProactiveProxyAuth = s_enableProactiveProxyAuth;
if (enableProactiveProxyAuth != -1)
{
return enableProactiveProxyAuth != 0;
}

// First check for the AppContext switch, giving it priority over the environment variable.
if (AppContext.TryGetSwitch(EnableProactiveProxyAuthCtxSwitch, out bool value))
{
s_enableProactiveProxyAuth = value ? 1 : 0;
}
else
{
// AppContext switch wasn't used. Check the environment variable.
s_enableProactiveProxyAuth =
Environment.GetEnvironmentVariable(EnableProactiveProxyAuthEnvironmentVariable) is string envVar &&
(envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)) ? 1 : 0;
}

return s_enableProactiveProxyAuth != 0;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Please move this to

and use the existing parsing helpers

private const string NtlmScheme = "NTLM";
private const string NegotiateScheme = "Negotiate";

private const string EnableProactiveProxyAuthCtxSwitch = "System.Net.Http.EnableProactiveProxyAuth";
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I'd call it System.Net.Http.SocketsHttpHandler.ProxyPreAuthenticate

Comment on lines +397 to +404
if (EnableProactiveProxyAuth)
{
NetworkCredential? credential = proxyCredentials.GetCredential(proxyUri, BasicScheme);
if (credential != null && credential != CredentialCache.DefaultNetworkCredentials)
{
SetBasicAuthToken(request, credential, isProxyAuth: true);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Could this call SendWithAuthAsync(preAuthenticate: EnableProactiveProxyAuth) instead and have it handle setting the headers since it already contains that logic?

private const string NtlmScheme = "NTLM";
private const string NegotiateScheme = "Negotiate";

private const string EnableProactiveProxyAuthCtxSwitch = "System.Net.Http.EnableProactiveProxyAuth";
Copy link
Member

Choose a reason for hiding this comment

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

There's a helper RuntimeSettingParser exactly for parsing AppContext and Env Var switches. Can it be used here?

// drop or reject unauthenticated connections (e.g., some HTTPS CONNECT tunnel proxies).
if (EnableProactiveProxyAuth)
{
NetworkCredential? credential = proxyCredentials.GetCredential(proxyUri, BasicScheme);
Copy link
Member

Choose a reason for hiding this comment

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

@wfurt, do we want this in general for proxies or only for CONNECT when establishing the tunnel?
Is this the right place to do it? Where would you actually put this code if we were to introduce it as a property like ProxyPreAuthenticate? Shouldn't this be rather in the method above?

await LoopbackServer.CreateServerAsync(async (proxyServer, proxyUri) =>
{
var psi = new ProcessStartInfo();
psi.Environment.Add("http_proxy", $"http://{proxyUri.Host}:{proxyUri.Port}");
Copy link
Member

Choose a reason for hiding this comment

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

There should also be test with the creds in the env var. Similar to the original issue.


namespace System.Net.Http.Functional.Tests
{
public abstract class ProactiveProxyAuthTest : HttpClientHandlerTestBase
Copy link
Member

Choose a reason for hiding this comment

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

I'd like to see coverage for both HTTP and HTTPS proxy as well as for HTTP and HTTPS requests (proxy request vs proxy tunnel), i.e. 4 cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Net.Http community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Authentication headers for HTTP Proxy are not sent proactively

7 participants