Skip to content

do not hand system proxy credentials to the origin server - #925

Merged
ok2c merged 1 commit into
apache:masterfrom
Hsnmohd:system-creds-proxy-target
Nov 13, 2026
Merged

ok2c merged 1 commit into
apache:masterfrom
Hsnmohd:system-creds-proxy-target

Conversation

@Hsnmohd

@Hsnmohd Hsnmohd commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

SystemDefaultCredentialsProvider, the default credentials provider of every client builder, falls back to asking java.net.Authenticator for RequestorType.PROXY credentials for whatever host issued the challenge, so with the common getRequestorType() == PROXY authenticator any origin server that answers 401 Basic is sent the JVM's proxy credentials (reproduced with HttpClients.createDefault() and HttpAsyncClients.createDefault(), direct and behind a proxy); skip the PROXY lookup when the auth scope is the host:port of the request being executed, which leaves 407 handling for forwarded and CONNECT requests unchanged.

@ok2c ok2c self-assigned this Nov 3, 2026
@ok2c

ok2c commented Nov 4, 2026

Copy link
Copy Markdown
Member

@Hsnmohd HttpClient always requests credentials for a given authentication scope consisting of a combination of a host name, a port, a realm and a scheme.

Conceptually, would consider it possible for a proxy and a server to use the same port on the same host and have different security model and access control?

@Hsnmohd

Hsnmohd commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Not really. A host:port is one listener. It can act as both a proxy and an origin (httpd with mod_proxy, say), but that's still one endpoint under one admin, and the realm is what separates its protection spaces. Keying credentials on (host, port, realm, scheme) is right, and this PR doesn't change that.

The leak comes from the requestor type, not the scope. On a 401 from an origin, the provider asks the Authenticator for SERVER and then for PROXY at the origin's host:port, which tells the Authenticator the origin is a proxy. The JDK's own clients only ask for PROXY on a 407 from the configured proxy, so the usual Authenticator hands out the proxy password whenever getRequestorType() == PROXY and never checks the host. With HttpClients.createDefault(), any server that answers 401 Basic gets that password.

getCredentials can't see whether the challenge was a 401 or a 407, so the PR relies on the point above: the host:port the current request is addressed to is the origin for that request, not its proxy, so it only gets the SERVER lookup. A 407 is scoped to the proxy's host:port, which differs from the request target for both forwarded requests and CONNECT, so proxy auth works as before.

@ok2c

ok2c commented Nov 6, 2026

Copy link
Copy Markdown
Member

The leak comes from the requestor type, not the scope.

@Hsnmohd There is no leak as long as proxy / server share the same host and port.

@ok2c

ok2c commented Nov 6, 2026

Copy link
Copy Markdown
Member

@Hsnmohd You still want the requester type to be factored in when requesting credentials, this needs to be solved differently. The correct way would be by extending AuthScope.

@Hsnmohd

Hsnmohd commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

@ok2c Fair, the host:port check was the wrong tool for that. Reworked it the way you suggested: AuthScope now carries an optional ChallengeType (null applies to both), Basic, Digest, Bearer and SCRAM set it from the challenge they processed, and SystemDefaultCredentialsProvider asks the Authenticator for SERVER on target challenges and PROXY on proxy challenges. Untyped scopes keep the existing SERVER-then-PROXY lookup.

The challenge type is left out of equals/hashCode/match, since including it breaks exact-scope lookups in existing providers (it tripped the eq(new AuthScope(...)) stubs in TestAuthenticationHandler). I can make it part of match if you want BasicCredentialsProvider to honor it too.

@ok2c

ok2c commented Nov 7, 2026

Copy link
Copy Markdown
Member

The challenge type is left out of equals/hashCode/match, since including it breaks exact-scope lookups in existing providers

@Hsnmohd These two scopes being equal, does not feel right. I think challenge type must be included in #equals, #hashCode and #match. If that breaks existing providers we need to figure out how to un-break them.

final AuthScope authScope1 = new AuthScope(host, getRealm(), getName(), ChallengeType.TARGET);
final AuthScope authScope2 = new AuthScope(host, getRealm(), getName(), ChallengeType.PROXY);

@ok2c

ok2c commented Nov 8, 2026

Copy link
Copy Markdown
Member

@Hsnmohd Please review #934

@Hsnmohd

Hsnmohd commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed #934, comments are there. Short version: Bearer and SCRAM drop the challenge type, and it outweighs host in match. #934 doesn't touch SystemDefaultCredentialsProvider, so the leak is still there with it alone. Once it lands I'll rebase this down to the provider change and its tests.

@ok2c

ok2c commented Nov 12, 2026

Copy link
Copy Markdown
Member

@Hsnmohd Please re-base your change-set on the latest master.

SystemDefaultCredentialsProvider asked java.net.Authenticator for
RequestorType.PROXY credentials whenever no SERVER credentials were
found, whatever host issued the challenge. With an Authenticator that
answers proxy requests, as is common for corporate proxies, any origin
server replying 401 Basic was sent the proxy credentials.

Use the challenge type now carried by AuthScope to pick the requestor
type: SERVER for target challenges, PROXY for proxy challenges. Untyped
scopes keep the SERVER-then-PROXY lookup.
@Hsnmohd
Hsnmohd force-pushed the system-creds-proxy-target branch from 00f7494 to 246e2f2 Compare November 13, 2026 09:14
@Hsnmohd

Hsnmohd commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@ok2c Rebased. It's down to the SystemDefaultCredentialsProvider change and its tests on top of #934: SERVER for TARGET scopes, PROXY for PROXY scopes, and untyped scopes keep the old SERVER-then-PROXY lookup. The new tests fail against master's provider and pass with this, and HttpClients.createDefault() / HttpAsyncClients.createDefault() no longer send the system proxy credentials to an origin answering 401 Basic, while 407s from the proxy still get them.

@ok2c
ok2c merged commit ef40283 into apache:master Nov 13, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants