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: |
…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 <kiryl.kavalenka@whiparound.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
…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 <kiryl.kavalenka@whiparound.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Upgrades the openshell PyPI wheel to 0.0.26 (from 0.0.22). Adds a patched openclaw sandbox template under packages/openshell/openclaw-sandbox/ with openclaw pinned to 2026.4.10, which includes the fetch-guard proxy fix (openclaw/openclaw#50650). The upstream OpenShell-Community template still pins openclaw@2026.3.11, where web_search/web_fetch bypass HTTPS_PROXY and fail with EAI_AGAIN in proxy-only environments (OpenShell sandboxes, corporate proxies). The template is exposed as a passthru attribute (openshell.openclawSandbox) so consumers can reference it with: openshell sandbox create --from ${openshell.openclawSandbox} ... Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Upgrades the openshell PyPI wheel to 0.0.26 (from 0.0.22). Adds a passthru attribute (openshell.openclawSandbox) that fetches the upstream OpenShell-Community openclaw sandbox template and patches the Dockerfile to pin openclaw@2026.4.10, which includes the fetch-guard proxy fix (openclaw/openclaw#50650). The upstream template still pins openclaw@2026.3.11, where web_search/web_fetch bypass HTTPS_PROXY and fail with EAI_AGAIN in proxy-only environments (OpenShell sandboxes, corporate proxies). Usage: openshell sandbox create --from ${openshell.openclawSandbox} ... Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Upgrades the openshell PyPI wheel to 0.0.26 (from 0.0.22). Adds a passthru attribute (openshell.openclawSandbox) that fetches the upstream OpenShell-Community openclaw sandbox template and patches the Dockerfile to pin openclaw@2026.4.10, which includes the fetch-guard proxy fix (openclaw/openclaw#50650). The upstream template still pins openclaw@2026.3.11, where web_search/web_fetch bypass HTTPS_PROXY and fail with EAI_AGAIN in proxy-only environments (OpenShell sandboxes, corporate proxies). Usage: openshell sandbox create --from ${openshell.openclawSandbox} ... Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…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 <kiryl.kavalenka@whiparound.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
…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 <kiryl.kavalenka@whiparound.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
…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 <kiryl.kavalenka@whiparound.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
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