Skip to content

fix(http): gate reqwest proxy env on NODE_USE_ENV_PROXY to match Node#5889

Open
jdalton wants to merge 1 commit into
PerryTS:mainfrom
jdalton:jdalton/node-use-env-proxy
Open

fix(http): gate reqwest proxy env on NODE_USE_ENV_PROXY to match Node#5889
jdalton wants to merge 1 commit into
PerryTS:mainfrom
jdalton:jdalton/node-use-env-proxy

Conversation

@jdalton

@jdalton jdalton commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Problem

perry's node:http/node:https/fetch bindings build their clients with reqwest::Client::builder() / Client::new() and never call .no_proxy(). reqwest honors HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY by default, so perry routes through the proxy unconditionally.

Real Node's built-in fetch (undici) and node:http/node:https ignore those env vars unless --use-env-proxy / NODE_USE_ENV_PROXY=1 is set (added in Node 24). Verified on Node v24.10.0 — without the flag, fetch/http.get connect directly; with it, they route through HTTP_PROXY.

So a program that does fetch(url) with HTTP_PROXY in the environment behaves differently on perry (uses the proxy) than on Node (ignores it) — a conformance divergence.

Fix

Gate the proxy-env behavior on NODE_USE_ENV_PROXY, matching Node's opt-in. Client builders in perry-ext-http now route through a small apply_node_proxy_policy(builder) helper that calls .no_proxy() by default and only leaves reqwest's env-proxy on when NODE_USE_ENV_PROXY=1. Applied at the three client-construction sites: the shared HTTP_CLIENT, per-agent clients (agent.rs), and per-request TLS clients (tls_client.rs), plus a policy-applied default_client() fallback.

Scope

perry-ext-http only — the node:http/https/fetch bindings, where Node conformance matters. perry-ext-axios and the audit CLI also use reqwest; left as follow-ups since they aren't node:* module surfaces.

Test

Pure unit test (proxy_policy_tests) covering the parsing — enabled iff the value is exactly "1", matching Node's NODE_* boolean-env convention. cargo test -p perry-ext-http passes.

Notes

  • NODE_USE_ENV_PROXY covers fetch/http/https, not raw sockets — same as Node.
  • The flag is experimental in Node 24; this just mirrors its semantics.

Summary by CodeRabbit

  • Bug Fixes
    • Updated HTTP client creation to follow the app’s proxy settings more consistently.
    • Improved fallback behavior so clients still use the same network settings if initial setup fails.
    • Refined how environment-based proxy options are interpreted, reducing unexpected proxy usage.

perry's node:http/fetch bindings use reqwest, which honors HTTP(S)_PROXY/NO_PROXY by default, so perry routed through the proxy unconditionally. Node's built-in fetch/http ignore those env vars unless NODE_USE_ENV_PROXY=1 (Node 24+). Gate the client builders on that flag (default .no_proxy(), honor env proxy only when set) so perry-ext-http matches Node. Adds a pure unit test for the parsing.

Signed-off-by: jdalton <john.david.dalton@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR centralizes Node-conformant proxy handling for reqwest clients in perry-ext-http. A new predicate and helper functions determine whether to disable standard proxy environment variables, apply this policy across the shared HTTP client, per-agent clients, and TLS client, and change build-failure fallbacks to a new default_client() function.

Changes

Proxy Policy Centralization

Layer / File(s) Summary
Proxy policy predicate and default client helper
crates/perry-ext-http/src/lib.rs
Adds proxy_enabled_from_env_value (checks NODE_USE_ENV_PROXY == "1"), apply_node_proxy_policy to disable proxy env usage via .no_proxy() when not opted in, default_client() fallback builder, and unit tests for the predicate.
Shared HTTP_CLIENT initialization
crates/perry-ext-http/src/lib.rs
Updates the lazy HTTP_CLIENT build to use apply_node_proxy_policy(...) and switches the build-failure fallback from reqwest::Client::new() to default_client().
Per-agent client construction
crates/perry-ext-http/src/agent.rs
Wraps the per-agent reqwest::Client::builder() chain with apply_node_proxy_policy(...) and changes the failure fallback to default_client().
TLS client builder
crates/perry-ext-http/src/tls_client.rs
Applies apply_node_proxy_policy(...) to TlsOptions::build_client's builder before TCP keepalive, TLS options, and pooling configuration.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant ClientBuilder as reqwest::ClientBuilder
    participant PolicyFn as apply_node_proxy_policy
    participant EnvVar as NODE_USE_ENV_PROXY
    participant Client as reqwest::Client

    Caller->>ClientBuilder: builder() (+ base config)
    Caller->>PolicyFn: apply_node_proxy_policy(builder)
    PolicyFn->>EnvVar: read env value
    alt value == "1"
        PolicyFn-->>Caller: builder (standard proxy env used)
    else not "1"
        PolicyFn->>ClientBuilder: no_proxy()
        PolicyFn-->>Caller: builder (proxy env disabled)
    end
    Caller->>ClientBuilder: build()
    alt build succeeds
        ClientBuilder-->>Caller: Client
    else build fails
        Caller->>Caller: default_client()
        Caller-->>Client: fallback Client
    end
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely captures the main change: gating reqwest proxy env handling on NODE_USE_ENV_PROXY to match Node.
Description check ✅ Passed The description covers the problem, fix, scope, and test plan, though it doesn't follow the template headings exactly.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-ext-http/src/lib.rs`:
- Around line 263-269: The fallback in default_client currently returns
reqwest::Client::new(), which bypasses the Node-conformant proxy policy applied
by apply_node_proxy_policy. Update the error path in default_client so it
rebuilds a client from the same builder with the same proxy settings, or
otherwise fails closed instead of constructing a fresh client that can re-enable
env proxy handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3881cadb-7bd3-4f9b-aecc-9f14f8d06763

📥 Commits

Reviewing files that changed from the base of the PR and between edd608a and 0156448.

📒 Files selected for processing (3)
  • crates/perry-ext-http/src/agent.rs
  • crates/perry-ext-http/src/lib.rs
  • crates/perry-ext-http/src/tls_client.rs

Comment on lines +263 to +269
/// A default reqwest client with the Node-conformant proxy policy applied.
/// Used as the fallback when a customized builder fails to build.
pub(crate) fn default_client() -> reqwest::Client {
apply_node_proxy_policy(reqwest::Client::builder())
.build()
.unwrap_or_else(|_| reqwest::Client::new())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file and nearby helper to understand fallback behavior.
FILE="crates/perry-ext-http/src/lib.rs"

echo "== Outline =="
ast-grep outline "$FILE" --view expanded || true

echo
echo "== Relevant lines =="
sed -n '1,340p' "$FILE" | cat -n | sed -n '1,340p'

echo
echo "== Search for proxy policy helper usage =="
rg -n "apply_node_proxy_policy|default_client|no_proxy|proxy" crates/perry-ext-http/src/lib.rs crates/perry-ext-http -g '!target' || true

Repository: PerryTS/perry

Length of output: 30069


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read only the relevant section if the file is large.
FILE="crates/perry-ext-http/src/lib.rs"
wc -l "$FILE"
sed -n '220,320p' "$FILE" | cat -n

Repository: PerryTS/perry

Length of output: 5108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the helper and any local tests/examples describing the intended proxy behavior.
rg -n "fn apply_node_proxy_policy|apply_node_proxy_policy|NO_PROXY|HTTP_PROXY|HTTPS_PROXY|Client::new\(" crates/perry-ext-http -g '!target' || true

echo
echo "== Cargo metadata for reqwest =="
rg -n '^reqwest\s*=|^name\s*=\s*"reqwest"|^version\s*=\s*"' Cargo.lock crates/perry-ext-http/Cargo.toml Cargo.toml -g '!target' || true

Repository: PerryTS/perry

Length of output: 36239


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="crates/perry-ext-http/src/lib.rs"
# Show the helper and default_client with surrounding context.
sed -n '1,340p' "$FILE" | nl -ba | sed -n '1,340p'

Repository: PerryTS/perry

Length of output: 191


🌐 Web query:

reqwest Client::new default proxy env vars no_proxy documentation

💡 Result:

Reqwest clients created via Client::new() (or the default ClientBuilder) automatically detect and use system proxy settings from environment variables [1][2]. When system proxy support is enabled (which is the default), the following environment variables are checked: 1. Proxy Configuration: HTTP_PROXY, http_proxy, HTTPS_PROXY, https_proxy, ALL_PROXY, and all_proxy [1][2]. 2. Exclusion List (no_proxy): The library checks NO_PROXY first, and if that is not set, it checks no_proxy [3][4][5]. The no_proxy settings follow these rules: - Entries must be comma-separated [4][5]. - An entry of * matches all hostnames [4][5]. - Domain names (e.g., example.com or .example.com) match that domain and all subdomains [4][5]. - IP addresses and subnet masks (e.g., 192.168.1.0/24) are supported [4][5]. If you wish to disable this automatic system proxy behavior, you can call .no_proxy() on the ClientBuilder [6][1]. Note that ClientBuilder::no_proxy() clears all proxies and stops automatic system proxy detection entirely; to configure specific proxy exclusions while still using a proxy, you should use the Proxy::no_proxy() method on your specific proxy configuration [6][7].

Citations:


Preserve the proxy policy in the fallback. reqwest::Client::new() re-enables reqwest’s default proxy env handling, so this error path can still honor HTTP_PROXY/HTTPS_PROXY/NO_PROXY and diverge from the Node-conformant .no_proxy() policy. Rebuild the same builder here or fail closed instead of returning that client.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-ext-http/src/lib.rs` around lines 263 - 269, The fallback in
default_client currently returns reqwest::Client::new(), which bypasses the
Node-conformant proxy policy applied by apply_node_proxy_policy. Update the
error path in default_client so it rebuilds a client from the same builder with
the same proxy settings, or otherwise fails closed instead of constructing a
fresh client that can re-enable env proxy handling.

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.

1 participant