Skip to content

fix(gix-transport): honor proxy configuration with reqwest - #3017

Draft
Sebastian Thiel (Byron) wants to merge 2 commits into
mainfrom
http-proxy-reqwest
Draft

Sebastian Thiel (Byron) wants to merge 2 commits into
mainfrom
http-proxy-reqwest

Conversation

@Byron

@Byron Byron commented Sep 23, 2026

Copy link
Copy Markdown
Member

Tasks

This section is for Byron only. Models continuing this PR must not add, remove, check, uncheck, rename, or reorder checkboxes here.

  • refackiew

Everything below this line was generated by Codex GPT-6.

Created by Codex on behalf of Byron. Byron will review before this is ready to merge.

Fixes #3015.

The blocking reqwest backend accepted http.proxy but could send the request directly. Build and reuse its client from the selected proxy and bypass settings, including explicit empty overrides and environment fallback.

Use reqwest's existing HTTP/HTTPS and SOCKS proxy support and the existing credential callback; no new packages or public options are needed. A direct dependency on reqwest's existing hyper-util provides its bypass matcher, so bypassed destinations skip proxy validation and credential lookup. Each accepted redirect selects its destination's proxy and reapplies proxy authentication. Origin credentials stay within their authority, bypassed origins receive no proxy credentials, and POST redirects retain the existing body rules. Environment-selected proxies keep their URL-specific credential helpers. Explicit origin authorization headers retain precedence over URL credentials, and setup errors release streamed uploads. Curl's configuration path is preserved.

Unsupported proxy schemes, Unix socket paths, SOCKS4 user IDs, and explicit Digest/Negotiate/NTLM authentication fail instead of silently ignoring the setting. The documented reqwest limits remain: AnyAuth and Basic use preemptive Basic authentication, and SOCKS currently requires a TLS-enabled build. Unused environment proxy errors are reported only if a request selects that scheme. Origin errors do not reject valid proxy credentials; CONNECT 407 handling checks the preserved source message because reqwest does not expose a typed status.

Recording-proxy tests run against both backends and compare the request target with git ls-remote using Git 2.54.0 (Apple Git-157). Git behavior was checked against http.c and t/t5564-http-proxy.sh at d38352cd43ab9745686d697872408bc3249a153f; omitted HTTP proxy ports use the documented libcurl default of 1080. The wire-level helper test is reqwest-specific because curl currently loses the helper password when its proxy URL contains a username; repository helper-configuration tests cover both backends.

Validation:

  • Reproduced the original direct-DNS failure before the fix and added regressions for substantive Codex review findings.
  • Full transport tests with plain reqwest, Rust TLS, and native TLS; curl HTTP suite including HTTPS redirects, authentication, bypass, and SOCKS.
  • Repository transport-option tests with both backends, including environment credential-helper selection.
  • Formatting, transport documentation, and library Clippy with --no-deps -- -D warnings. Existing repository/test lint warnings remain unchanged.
  • All substantive findings from completed Codex reviews were addressed. The review of ef61e17 was attempted once but interrupted by the account usage limit; the final revision has the local validation listed above.

@Byron

Byron commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

Supersedes #3016.

Codex

#3016 fixes a narrower problem: wiring configured proxies into reqwest. My #3017 also adds authentication and Git/curl compatibility behavior.

                                                          #3016                                       #3017
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Total diff                                               +245 / −48, 3 files                         +1,542 / −78, 9 files
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 Added test lines                                         125                                         About 1,100
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 Explicit HTTP/HTTPS proxy; empty value disables it       Supported                                   Supported
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 Proxy credentials                                        Credentials embedded in URL                 Also credential helpers and approve/reject handling
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 SOCKS                                                    Rejected                                    Supported, currently requires TLS feature
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 Configured bypass with environment-selected proxy        Not handled when Options::proxy is unset    Handled
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 no_proxy=* with numeric IP destinations                  Inherits reqwest’s gap                      Fixed
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 Proxy authentication across redirects to another host    Can lose authentication                     Reapplied for each destination
───────────────────────────────────────────────────────  ──────────────────────────────────────────  ─────────────────────────────────────────────────────
 Omitted HTTP proxy port                                  80                                          Git/libcurl’s 1080

The main implementation expansion is manual redirect handling. Reqwest removes proxy authorization when the destination authority changes without restoring it. Working around that also requires preserving POST redirect rules and protecting origin credentials. Another roughly 80 lines handle credential helpers for environment-selected proxies.

Ours also adds a direct hyper-util dependency to reuse its bypass matcher; it was already a transitive dependency.

For issue #3015 alone, #3016 is the more proportionate fix. Your additional curl/Git requirement motivated real compatibility work, but I expanded the patch substantially. The redirect workaround is the largest maintenance cost and deserves separate scrutiny. Even ours stops short of full curl parity: Digest/Negotiate/NTLM are explicitly rejected, while Basic is supported.

The blocking `reqwest` backend received `http::Options` but constructed
its client before seeing them. A configured `http.proxy` was silently
ignored, allowing direct requests even when only the proxy could reach
the destination. The regression fails with a direct DNS error against
`git.invalid` before this change.

Configure the client from the effective proxy and bypass list, preserving
connection reuse until that configuration or its credentials change.
Explicit configuration overrides the environment, an empty proxy disables
proxying, and `no_proxy=*` also bypasses numeric addresses. Preserve the
configuration when restarting a failed worker so invalid proxy settings
cannot turn into a direct connection on retry.

Select environment proxies for each actual request URL, after request
hooks and accepted redirects. Explicit proxy configuration still applies
to both schemes. Use Git/libcurl's documented port 1080 for HTTP proxies
without a port, preserving explicit ports and the usual HTTPS default.

For `reqwest`, leave imported environment proxy values out of explicit
transport overrides so redirects can select the destination scheme. Keep
`curl`'s existing setup. Prepare credential helpers for the imported proxy
candidates so username-only environment URLs still obtain the matching
password, including after a redirect. Preserve explicit empty settings
while treating empty environment values as absent during fallback.

Validate environment proxies only when their destination scheme is
actually requested, including redirects and URL-changing request hooks.
An unused scheme's proxy must not break a valid request, and a configured
global bypass must ignore disabled proxies entirely.

Reuse `reqwest` for proxy routing, Basic credentials and SOCKS, and use
the existing credential helper callback to obtain, approve or reject
proxy credentials. Reject unsupported schemes, Unix socket paths, SOCKS4
user IDs (which reqwest otherwise silently drops), and
explicit authentication methods that `reqwest` cannot honor. Document
its preemptive Basic handling of `anyauth` and its current requirement
for a TLS feature when using SOCKS.

`reqwest` strips proxy authentication on authority-changing redirects
without reapplying it. Execute each accepted hop separately, retaining
the existing redirect safety policy and reapplying proxy authentication
for that destination. Preserve POST-to-GET conversion, replay buffered
307/308 bodies, and stop when streamed bodies cannot be replayed. Do not
carry origin credentials across authorities or proxy headers to bypassed
origins. Reuse helper credentials while the selected proxy stays the same.
Preserve explicit `Authorization` headers over credentials embedded in the
origin URL, matching the previous request-builder precedence.

Use the bypass matcher from `reqwest`'s existing `hyper-util` dependency
before validating or authenticating a proxy. A bypassed destination must
not need proxy credentials or store unvalidated ones. This introduces a
direct dependency but no new packages. Close streamed upload bodies before
reporting proxy setup errors, so the writer and header reader cannot block
waiting for each other.

Approve proxy credentials even when the origin returns an error, reject
them only for a proxy authentication response (407), and leave them alone
when a connection fails without a response. Regression tests cover the
redirect, default-port and credential rejection findings from review.
Include HTTPS CONNECT rejections: `reqwest` hides their status and
hyper-util does not export its error type, so match the specific source
message until a typed API becomes available. Test 407, other tunnel
failures and dropped connections to protect that distinction.

Git reference: `http.c` and `t/t5564-http-proxy.sh` at
`d38352cd43ab9745686d697872408bc3249a153f`. The recording-proxy regression
also runs `git ls-remote` with Git 2.54.0 (Apple Git-157) and verifies the
same absolute request target. Routing, bypass, environment precedence,
inline authentication and SOCKS tests are shared with the curl backend;
the credential-helper regression is specific to reqwest because `curl`
currently loses the helper password when its proxy URL has a username.

Validation:

- Reproduced the issue with the failing recording-proxy test first.
- Regressions cover unused proxy settings, global bypass of invalid
  settings, SOCKS4 user IDs, request-hook URL changes, environment helper
  selection, authenticated redirects, buffered/streamed POST redirects,
  explicit authorization precedence, and upload setup failures.
- Transport tests with plain reqwest, Rust TLS and native TLS.
- The complete curl HTTP transport integration suite, including HTTPS
  proxy selection after redirects.
- Repository transport-option tests with both reqwest and curl, including
  the imported-environment regression from review.
- `cargo fmt --all -- --check` and transport documentation generation.
- Library Clippy with `--no-deps -- -D warnings`; test Clippy completes
  with existing warnings in the older transport tests.
Libcurl prioritizes user information in `http.proxy` over
`CURLOPT_PROXYUSERNAME` and `CURLOPT_PROXYPASSWORD`. A URL such as
`http://user@proxy` therefore discarded the helper's password and sent
`user:` instead.

Like Git, remove URL credentials before passing the proxy to libcurl
when a helper is configured, and keep using the dedicated credential
options. Preserve explicit empty proxies and use `gix-url` to retain
the proxy scheme, host, port and path.

Run the existing recording-proxy credential check against both backends.
It reproduced the empty password with curl before the fix; reqwest's
helper credentials already worked for HTTP, HTTPS CONNECT, and
environment-selected proxies.

Validation: 39 curl and 46 reqwest transport tests passed with Rust TLS,
along with formatting and strict library Clippy. Clippy reports the
existing removed-lint warning.
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.

blocking-http-transport-reqwest silently ignores http.proxy (unlike the curl backend)

2 participants