fix(infra/net): use EnvHttpProxyAgent when proxy env vars are configured#50650
fix(infra/net): use EnvHttpProxyAgent when proxy env vars are configured#50650obviyus merged 3 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR routes Key issues:
Confidence Score: 2/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/infra/net/fetch-guard.ts
Line: 198
Comment:
**`ALL_PROXY` triggers `EnvHttpProxyAgent` but is silently ignored by it**
`hasProxyEnvConfigured()` (in `proxy-env.ts`) returns `true` for `ALL_PROXY`/`all_proxy`, but your own codebase documents that `EnvHttpProxyAgent` ignores `ALL_PROXY` (see the comment in `resolveEnvHttpProxyUrl`):
```
// - ALL_PROXY is ignored by EnvHttpProxyAgent
```
So when *only* `ALL_PROXY` is set (no `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY` variants), this new branch fires:
1. `hasProxyEnvConfigured()` returns `true`
2. `new EnvHttpProxyAgent()` is selected — bypassing `createPinnedDispatcher`
3. `EnvHttpProxyAgent` has no configured proxy (it ignored `ALL_PROXY`), so it falls back to a **direct connection without the pinned lookup**
4. DNS is re-resolved freely at connection time — the anti-DNS-rebinding protection provided by `createPinnedDispatcher` is silently lost
The fix should use `hasEnvHttpProxyConfigured()` (already exported from `proxy-env.ts`) which only returns `true` when `EnvHttpProxyAgent` would actually route through a proxy:
```suggestion
if (canUseTrustedEnvProxy || hasEnvHttpProxyConfigured()) {
```
This ensures the branch is only taken when a proxy will actually be used, preserving DNS-pinning protection for `ALL_PROXY`-only configurations.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/infra/net/fetch-guard.ts
Line: 196-198
Comment:
**`STRICT` mode silently loses DNS-pinning protection when proxy env vars are present**
The separation between `STRICT` and `TRUSTED_ENV_PROXY` modes was architecturally intentional. The whole point of the pinned dispatcher in `STRICT` mode is to prevent DNS rebinding attacks: DNS is resolved once, validated against the SSRF policy, and then the connection is pinned to that exact IP so a racing DNS change cannot redirect the connection to an internal address.
When `EnvHttpProxyAgent` is used instead, the CONNECT tunnel carries the **original hostname** to the proxy, which then performs its own DNS resolution at connection time. The validated IP from `resolvePinnedHostnameWithPolicy` is never actually used for the connection. This means:
1. `resolvePinnedHostnameWithPolicy("evil-rebinding.example")` resolves to `1.2.3.4` → passes SSRF check ✓
2. DNS TTL expires; attacker's nameserver now points `evil-rebinding.example` → `192.168.1.10`
3. `EnvHttpProxyAgent` sends `CONNECT evil-rebinding.example:443` → proxy resolves → `192.168.1.10`
4. SSRF protection bypassed
`TRUSTED_ENV_PROXY` mode was an **explicit operator opt-in** that accepted this trade-off. This PR promotes the same weakened posture to `STRICT` mode for any deployment with proxy env vars — without any explicit acknowledgement in the mode name or call sites.
A safer approach that preserves the mode boundary would be to create the dispatcher via `createPinnedDispatcher` with `{ mode: "env-proxy" }`:
```typescript
} else if (params.pinDns !== false) {
const proxyPolicy = hasEnvHttpProxyConfigured()
? { ...params.dispatcherPolicy, mode: "env-proxy" as const }
: params.dispatcherPolicy;
dispatcher = createPinnedDispatcher(pinned, proxyPolicy, params.policy);
}
```
This routes through the proxy while still threading the pinned `lookup` through the `connect` option of `EnvHttpProxyAgent`, preserving the DNS-pinning contract at the undici layer for the proxy connection itself.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/infra/net/fetch-guard.ts
Line: 196-198
Comment:
**`canUseTrustedEnvProxy` is now dead code**
Since the updated condition is `canUseTrustedEnvProxy || hasProxyEnvConfigured()`, and `canUseTrustedEnvProxy` is defined as `mode === GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY && hasProxyEnvConfigured()`, it is always a strict subset of `hasProxyEnvConfigured()`. The variable can never be `true` when `hasProxyEnvConfigured()` is `false`, so it no longer changes the branching outcome. The variable (and the double call to `hasProxyEnvConfigured()`) can be removed:
```suggestion
const hasProxy = hasProxyEnvConfigured();
if (hasProxy) {
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "fix(infra/net): use ..." |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39561aead5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Thanks for the thorough review. All three points were valid — pushed an updated commit addressing all of them: 1. 2. DNS-rebinding protection — Replaced the bare 3. Dead code — The updated approach: STRICT mode + proxy env → pinned dispatcher with |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a2f5e5f43
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
7a2f5e5 to
ac8ba11
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac8ba11384
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
ac8ba11 to
85e8024
Compare
|
Rebased on latest main ( Regarding the Codex P1 about DNS pinning being lost in |
85e8024 to
1e3f3f5
Compare
|
Pushed fix for the protocol-sensitive proxy detection — now passes |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e3f3f5304
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
1e3f3f5 to
7606a26
Compare
|
Pushed fix for the explicit-proxy and direct-fallback concerns:
|
76a3025 to
3b3ae0a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b3ae0a9c4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
3b3ae0a to
633eabd
Compare
|
Added |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 633eabdf3b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
9fab5b2 to
9423591
Compare
|
Added Note: |
9423591 to
6cdd822
Compare
|
Bumping this — the PR has 7 bot reviews but zero human reviews so far. This fix is critical for anyone running OpenClaw behind an HTTP CONNECT proxy (e.g., OpenShell sandboxes, corporate networks). Without it, The latest commit from @kkav004 addresses the Would really appreciate human eyes on this. Happy to test any requested changes. |
|
Also cc @scoootscooob @gumadeiras — would appreciate a human review on this one. It's been open since March 19 with only bot reviews so far. |
…ng DNS pinning When HTTP_PROXY/HTTPS_PROXY env vars are configured, the SSRF guard's pinned dispatcher connects directly to the DNS-resolved IP, bypassing the proxy. This fails in environments where direct outbound connections are blocked (OpenShell sandboxes, Docker containers, corporate networks). Use `createPinnedDispatcher` with `mode: "env-proxy"` when `hasEnvHttpProxyConfigured()` returns true. This preserves DNS-pinning (the resolved IP is threaded into the connect option via `EnvHttpProxyAgent`) while routing through the proxy. - Uses `hasEnvHttpProxyConfigured()` (not `hasProxyEnvConfigured()`) to avoid the ALL_PROXY edge case where EnvHttpProxyAgent ignores ALL_PROXY - Preserves STRICT mode's anti-DNS-rebinding guarantee - TRUSTED_ENV_PROXY remains the explicit opt-in for unpinned proxy routing - No change when proxy env vars are not set Fixes openclaw#47598, openclaw#49948, openclaw#32947, openclaw#46306 Related: openclaw#45248
6cdd822 to
28528cb
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 28528cb2fe
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Addressed the latest review thread in 3a37bf4: the direct-path bypass is now keyed to the current request hostname instead of any non-empty |
obviyus
left a comment
There was a problem hiding this comment.
Reviewed latest changes; landing now.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a37bf4c3e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const dispatcherPolicy: PinnedDispatcherPolicy | undefined = useEnvProxy | ||
| ? Object.assign({}, params.dispatcherPolicy, { mode: "env-proxy" as const }) | ||
| : params.dispatcherPolicy; | ||
| dispatcher = createPinnedDispatcher(pinned, dispatcherPolicy, params.policy); |
There was a problem hiding this comment.
Keep strict SSRF fetches bound to the pinned destination
Switching STRICT requests to mode: "env-proxy" here drops the DNS-to-connect binding that strict mode relied on. resolvePinnedHostnameWithPolicy() validates one DNS answer, but for proxied traffic undici routes through EnvHttpProxyAgent/ProxyAgent, where the proxy path does not honor the pinned connect.lookup for selecting the origin endpoint, so the actual CONNECT target is chosen later from hostname/proxy resolution. In proxy-enabled deployments, a DNS change between validation and connect can bypass strict destination pinning.
Useful? React with 👍 / 👎.
| const useEnvProxy = | ||
| hasEnvHttpProxyConfigured(protocol) && | ||
| !params.dispatcherPolicy?.mode && | ||
| !keepsTrustedHostOnDirectPath(parsedUrl.hostname, params.policy); |
There was a problem hiding this comment.
Fallback to direct pinning when env proxy config is invalid
This branch now forces STRICT mode onto env-proxy whenever a proxy env var is merely non-empty, but it never validates that the env proxy settings can actually construct an EnvHttpProxyAgent. With malformed inherited HTTP_PROXY/HTTPS_PROXY, guarded fetches now fail during dispatcher creation instead of using the prior direct pinned path, which can break unrelated web_fetch/media requests in otherwise direct-connect environments.
Useful? React with 👍 / 👎.
…kav004) * fix(infra/net): route through env proxy in STRICT mode while preserving DNS pinning When HTTP_PROXY/HTTPS_PROXY env vars are configured, the SSRF guard's pinned dispatcher connects directly to the DNS-resolved IP, bypassing the proxy. This fails in environments where direct outbound connections are blocked (OpenShell sandboxes, Docker containers, corporate networks). Use `createPinnedDispatcher` with `mode: "env-proxy"` when `hasEnvHttpProxyConfigured()` returns true. This preserves DNS-pinning (the resolved IP is threaded into the connect option via `EnvHttpProxyAgent`) while routing through the proxy. - Uses `hasEnvHttpProxyConfigured()` (not `hasProxyEnvConfigured()`) to avoid the ALL_PROXY edge case where EnvHttpProxyAgent ignores ALL_PROXY - Preserves STRICT mode's anti-DNS-rebinding guarantee - TRUSTED_ENV_PROXY remains the explicit opt-in for unpinned proxy routing - No change when proxy env vars are not set Fixes openclaw#47598, openclaw#49948, openclaw#32947, openclaw#46306 Related: openclaw#45248 * test(infra): stabilize fetch guard proxy assertions * fix: respect hostname-scoped proxy bypass (openclaw#50650) (thanks @kkav004) --------- Co-authored-by: Kiryl Kavalenka <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]>
…kav004) * fix(infra/net): route through env proxy in STRICT mode while preserving DNS pinning When HTTP_PROXY/HTTPS_PROXY env vars are configured, the SSRF guard's pinned dispatcher connects directly to the DNS-resolved IP, bypassing the proxy. This fails in environments where direct outbound connections are blocked (OpenShell sandboxes, Docker containers, corporate networks). Use `createPinnedDispatcher` with `mode: "env-proxy"` when `hasEnvHttpProxyConfigured()` returns true. This preserves DNS-pinning (the resolved IP is threaded into the connect option via `EnvHttpProxyAgent`) while routing through the proxy. - Uses `hasEnvHttpProxyConfigured()` (not `hasProxyEnvConfigured()`) to avoid the ALL_PROXY edge case where EnvHttpProxyAgent ignores ALL_PROXY - Preserves STRICT mode's anti-DNS-rebinding guarantee - TRUSTED_ENV_PROXY remains the explicit opt-in for unpinned proxy routing - No change when proxy env vars are not set Fixes openclaw#47598, openclaw#49948, openclaw#32947, openclaw#46306 Related: openclaw#45248 * test(infra): stabilize fetch guard proxy assertions * fix: respect hostname-scoped proxy bypass (openclaw#50650) (thanks @kkav004) --------- Co-authored-by: Kiryl Kavalenka <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]>
Problem
When
HTTPS_PROXY/HTTP_PROXYenvironment variables are set,web_fetch(and other tools usingfetchWithSsrFGuardwithout an explicitdispatcherPolicy.mode) fail withfetch failedorgetaddrinfo EAI_AGAIN. The pinned dispatcher connects directly to the DNS-resolved IP, bypassing the proxy entirely.This breaks OpenClaw in:
The
TRUSTED_ENV_PROXYmode (#45248) works correctly but requires explicit opt-in. The defaultSTRICTmode always usescreatePinnedDispatcherindirectmode, which bypasses the proxy.Fix
Two files changed.
src/infra/net/fetch-guard.ts— WhenhasEnvHttpProxyConfigured()returnstrueand the caller hasn't already specified adispatcherPolicy.modeor allowed private networks, usecreatePinnedDispatcherwithmode: "env-proxy"instead of the defaultdirectmode:src/infra/net/fetch-guard.ssrf.test.ts— Updated test expectations to reflect that STRICT mode now usesEnvHttpProxyAgent(via pinnedenv-proxydispatcher) when proxy env vars are configured:What's preserved:
createPinnedDispatcherthreads the pinned lookup intoEnvHttpProxyAgentviaconnect: withPinnedLookup()in its existingenv-proxymoderesolvePinnedHostnameWithPolicy()still runs before the dispatcher is createdmode: "explicit-proxy"(Telegram media),mode: "direct"(fallback recovery), or any other mode are not overriddenallowPrivateNetworkordangerouslyAllowPrivateNetworkstay on the direct pinned path (BlueBubbles, remote memory over LAN)hasEnvHttpProxyConfigured(protocol)sohttp://URLs only trigger env-proxy whenHTTP_PROXYis setdirectmode behaviorHow I found this
Deploying NemoClaw (NVIDIA's OpenClaw wrapper) inside an OpenShell sandbox. The sandbox has
NODE_USE_ENV_PROXY=1,HTTPS_PROXY, andNODE_EXTRA_CA_CERTSconfigured — rawfetch()works natively. Butweb_fetchfails because the SSRF guard's pinned dispatcher indirectmode bypasses the proxy.The
env-proxymode already existed increatePinnedDispatcher(inssrf.ts) and correctly threads DNS pinning throughEnvHttpProxyAgent. This PR just activates it when proxy env vars are detected and the caller hasn't opted out.Testing
fetch-guard.ssrf.test.tspass locallypnpm checkpasses locally (format, types, lint, all boundary checks)web_fetchworks for google.com, httpbin.org, bbc.com, api.github.com in an OpenShell sandboxdirectmode (unchanged)dispatcherPolicy.mode→ not overriddenallowPrivateNetwork/dangerouslyAllowPrivateNetwork→ direct pinned path preservedFixes