Skip to content

fix(networking): honor HTTP(S)_PROXY in HttpClientBuilder.Build() - #6670

Open
schonmann wants to merge 4 commits into
stacklok:mainfrom
schonmann:fix/build-honor-http-proxy
Open

schonmann wants to merge 4 commits into
stacklok:mainfrom
schonmann:fix/build-honor-http-proxy

Conversation

@schonmann

@schonmann schonmann commented Sep 15, 2026

Copy link
Copy Markdown

Problem

  • HttpClientBuilder.Build() builds its http.Transport without a Proxy field.
  • That means clients it produces ignore HTTP_PROXY / HTTPS_PROXY / NO_PROXY (a zero-value http.Transport has Proxy: nil, unlike http.DefaultTransport).
  • The embedded auth server's upstream OAuth/DCR client is built this way (pkg/authserver/upstream/oauth2.gonewHTTPClientForHostNewHostScopedClientBuilder().Build()).
  • Where the upstream is only reachable via an explicit forward proxy, the token-exchange and DCR calls bypass the proxy and fail even with the proxy env vars set.
  • The vMCP tool-forwarding path is unaffected, since it uses CloneDefaultTransportWithDialControl, which already sets Proxy: http.ProxyFromEnvironment.
flowchart LR
    Pod["MCPRemoteProxy pod<br/>(HTTPS_PROXY set)"]

    Pod -->|"tool-forwarding client<br/>CloneDefaultTransportWithDialControl"| P1["honors proxy"]
    Pod -->|"auth-server OAuth client<br/>Build → Proxy: nil"| P2["ignores proxy"]

    P1 --> FP["forward proxy"] --> UP1["upstream MCP host<br/>reachable"]
    P2 --> DIRECT["direct egress"] --> UP2["IP-allowlisted token<br/>endpoint → 403"]

    classDef ok fill:#e7f7ec,stroke:#2e7d32,color:#1b3d24;
    classDef bad fill:#fdecea,stroke:#c62828,color:#5b1a15;
    class P1,FP,UP1 ok;
    class P2,DIRECT,UP2 bad;
Loading

Fixes #6671

Fix

  • Set Proxy: http.ProxyFromEnvironment on the transport in Build(), matching http.DefaultTransport and the vMCP path.
  • Adds a regression test asserting the built transport sets Proxy.

Note

  • When the proxy address is itself private, the private-IP dial guard blocks the proxy connection.
  • That case is covered by the existing AllowPrivateIPs option.

Type of change

  • Bug fix

Test plan

  • Unit tests (task test) — added TestHttpClientBuilder_BuildSetsProxyFromEnvironment

Note: the suite was not run on the authoring machine (no Go toolchain available there); relying on CI to execute task test / task lint-fix.

API Compatibility

  • This PR does not break the v1beta1 API (it only touches pkg/networking).

Does this introduce a user-facing change?

Yes — HTTP clients built via pkg/networking (including the auth server's upstream OAuth/DCR calls) now honor HTTP(S)_PROXY / NO_PROXY, so deployments behind an explicit forward proxy work as expected.

Build constructed its http.Transport without a Proxy field, so clients it
produced ignored HTTP_PROXY/HTTPS_PROXY/NO_PROXY (a zero-value transport has
Proxy: nil, unlike http.DefaultTransport). The auth server's upstream OAuth/DCR
client is built via NewHostScopedClientBuilder().Build(), so it bypassed an
explicit forward proxy. Set Proxy: http.ProxyFromEnvironment, matching
http.DefaultTransport and CloneDefaultTransportWithDialControl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Antonio Schonmann <antonio.schonmann@trmlabs.com>
@schonmann
schonmann marked this pull request as ready for review September 15, 2026 23:47
Copilot AI lite review requested due to automatic review settings September 15, 2026 23:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes HttpClientBuilder.Build() so HTTP clients created via pkg/networking honor HTTP_PROXY / HTTPS_PROXY / NO_PROXY, aligning behavior with http.DefaultTransport and the existing vMCP transport path; this unblocks proxy-only upstream OAuth/DCR calls from the embedded auth server.

Changes:

  • Set http.Transport.Proxy = http.ProxyFromEnvironment in HttpClientBuilder.Build().
  • Add a regression test asserting the built transport has a non-nil Proxy function.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pkg/networking/http_client.go Ensures builder-created transports use proxy env vars by wiring ProxyFromEnvironment.
pkg/networking/http_client_test.go Adds a regression test to prevent dropping the proxy behavior in future refactors.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/networking/http_client_test.go
@schonmann

Copy link
Copy Markdown
Author

Hi team, quick pull request for your consideration! 👋

In essence -> this will honor the HTTP(S)_PROXY env, making token endpoints from MCPs behind firewall and with a strict source-IP allowlist (i.e. including proxy's IP range) functional.

Thx!

Addresses review feedback: avoid a panic on structural change and give a
clearer failure message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Antonio Schonmann <antonio.schonmann@trmlabs.com>
@jhrozek

jhrozek commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Thanks for the patch. Do you think it's safe to always pass on the environment proxy variables?
What about adding a new option along the lines of the existing WithPrivateIPs something like WithProxyFromEnvironment that would enable the proxy environment values only when set?

@schonmann

Copy link
Copy Markdown
Author

Hey, thanks for checking @jhrozek!

Dug into this a bit, and I think always passing is actually a safe choice here. Why:

  • From what I can tell, Build() was the only spot in pkg/networking that wasn’t honoring the proxy. CloneDefaultTransportWithDialControl and Go’s own http.DefaultTransport already set Proxy: ProxyFromEnvironment.
  • The "enable only when set" behavior kind of comes for free: ProxyFromEnvironment returns a nil URL when no HTTP(S)_PROXY is exported, and the net/http docs say "if Proxy is nil or returns a nil *URL, no proxy is used".
  • With no env var it behaves exactly like today, and NO_PROXY handles the opt out.

That's why I'd lean away from a separate WithProxyFromEnvironment flag in this case, specifically. Thoughts?

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.

HttpClientBuilder.Build() ignores HTTP(S)_PROXY (auth-server OAuth/DCR calls bypass forward proxy)

3 participants