fix(doctor): warn when HTTP(S)_PROXY is set without tools.web.fetch.useTrustedEnvProxy#96172
fix(doctor): warn when HTTP(S)_PROXY is set without tools.web.fetch.useTrustedEnvProxy#96172nxmxbbd wants to merge 5 commits into
Conversation
…seTrustedEnvProxy Surface a doctor / status / security-audit warning when HTTP_PROXY, HTTPS_PROXY, http_proxy, or https_proxy is present in the Gateway process environment while tools.web.fetch.useTrustedEnvProxy is not enabled. web_fetch silently uses direct connections in that case, leading to confusing timeout failures on networks that block direct egress (e.g. behind a corporate firewall or regional proxy). Detection delegates to hasEnvHttpProxyAgentConfigured() from src/infra/net/proxy-env.ts so the warning gate mirrors what undici's EnvHttpProxyAgent would actually pick up at runtime: lowercase takes precedence over uppercase, empty lowercase shadows uppercase as intentional unset, and ALL_PROXY is intentionally not counted because EnvHttpProxyAgent does not honor it for HTTP/HTTPS. The change adds a diagnostic only; web_fetch routing and the SSRF DNS-pinning boundary are unchanged. Mirrors the prior openclaw#74271 pattern (warn when OPENCLAW_GATEWAY_TOKEN env shadows gateway.auth.token config) so the diagnostic appears in openclaw doctor, openclaw status, and openclaw security audit --deep. Fixes openclaw#95560.
ec2e77c to
76a9fec
Compare
|
Codex review: found issues before merge. Reviewed July 16, 2026, 6:58 AM ET / 10:58 UTC. Summary PR surface: Source +122, Tests +556. Total +678 across 7 files. Reproducibility: yes. for the diagnostic condition: source inspection and built-CLI output establish that HTTP(S) proxy variables are ignored by the per-tool path without the opt-in. The original blocked-egress timeout itself was not demonstrated against a live corporate or regional proxy. Review metrics: 2 noteworthy metrics.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review findings
Review detailsBest possible solution: Use one canonical proxy-routing diagnostic that recognizes the managed proxy first, offers the legacy per-tool opt-in only when appropriate, suppresses false warnings when central routing is active, and covers that behavior in doctor, status, and deep-audit tests. Do we have a high-confidence way to reproduce the issue? Yes for the diagnostic condition: source inspection and built-CLI output establish that HTTP(S) proxy variables are ignored by the per-tool path without the opt-in. The original blocked-egress timeout itself was not demonstrated against a live corporate or regional proxy. Is this the best way to solve the issue? No, not against current Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against b1e814f57a7c. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +122, Tests +556. Total +678 across 7 files. View PR surface stats
Security concerns:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
This pull request has been automatically marked as stale due to inactivity. |
…y target matchesNoProxy() treats a bare * NO_PROXY value as bypass-everything, so enabling tools.web.fetch.useTrustedEnvProxy in that environment would not route any web_fetch request through the proxy and the remediation was misleading. Gate the doctor/status/security-audit diagnostic on the same all-bypass contract (lower-case no_proxy shadows upper-case, * entries inside lists are still per-entry skipped and keep warning), and cover the negative and permutation cases across all three surfaces.
What Problem This Solves
Fixes an issue where users with
HTTP_PROXY/HTTPS_PROXY(or their lowercase variants) set in the Gateway process environment seeweb_fetchsilently use direct connections instead of routing through the configured proxy. On networks that block direct egress (corporate firewalls, regional proxies), this causes confusingweb_fetchtimeouts with no operator feedback about the missingtools.web.fetch.useTrustedEnvProxyopt-in.The
tools.web.fetch.useTrustedEnvProxyoption has existed since the original landing for this surface, but there is no in-product surface that tells operators it exists or that their proxy env vars are being ignored. Users discover the option via changelogs, GitHub issues, or community channels.Closes #95560.
Why This Change Was Made
Adds a diagnostic warning that fires in three existing surfaces —
openclaw doctor,openclaw status, andopenclaw security audit --deep— when any ofHTTP_PROXY,HTTPS_PROXY,http_proxy, orhttps_proxyis present in the Gateway process environment buttools.web.fetch.useTrustedEnvProxyis not enabled. The change is diagnostic-only:web_fetchrouting is unchangedsrc/infra/net/fetch-guard.tsis unchangedDetection delegates to the existing per-protocol predicate
hasEnvHttpProxyConfigured("http", env) || hasEnvHttpProxyConfigured("https", env)fromsrc/infra/net/proxy-env.ts. Theweb_fetchruntime resolvestools.web.fetch.useTrustedEnvProxyinsrc/agents/tools/web-fetch.tsand passes that value into the guarded fetch env-proxy dispatch path insrc/infra/net/fetch-guard.ts, which callsshouldUseEnvHttpProxyForUrl()before constructing the proxy dispatcher. The warning gate therefore stays tied to the HTTP(S) env vars that theweb_fetchdispatcher considers:ALL_PROXY/all_proxyalone do not fire — the per-protocol helper only readsHTTP_PROXY/HTTPS_PROXY/http_proxy/https_proxy, and theweb_fetchdispatcher (built with no explicit options atfetch-guard.ts:528) does not honorALL_PROXYfor HTTP/HTTPS even whenuseTrustedEnvProxyis on,NO_PROXY=*/no_proxy=*value suppresses the warning —matchesNoProxy()treats that exact value as bypass-everything, so enabling the opt-in could not route anyweb_fetchrequest through the proxy and the recommendation would be misleading.*entries inside comma/whitespace-separated lists are skipped per-entry by that matcher (only the bare value is all-bypass), so partialNO_PROXYlists still warn.Prior art in this repo: this PR mirrors the shape of #74271 (PR #74433,
fix(doctor): warn when OPENCLAW_GATEWAY_TOKEN env overrides gateway.auth.token config) — pure resolver insrc/gateway/+ integration into the same three doctor / status / security-audit surfaces + per-surface unit tests +severity: "warn". The remediation text follows the canonical SSRF framing already used by thedocs/tools/web-fetch.mdtrusted-env-proxy guidance.Scope boundaries (deliberate)
src/gateway/web-fetch-proxy-source-conflict.tsto mirror the [Feature]: doctor/status should warn when OPENCLAW_GATEWAY_TOKEN in ~/.openclaw/.env overrides gateway.auth.token #74271 precedent path (src/gateway/auth-token-source-conflict.ts). The subject is atools.web.fetch.*concern, sosrc/agents/tools/would also be a defensible location; the precedent path is kept for minimum-diff review against the precedent.tools.web.fetch.useTrustedEnvProxy: falsealso produces the warning. The runtime gate atweb-fetch.tsis=== true, soundefinedand explicitfalserouteweb_fetchdirect identically; treating them the same in the diagnostic is consistent with runtime behavior. Operators who explicitly opted out for SSRF safety see one informational line perdoctorrun rather than a silent miss; this matches the precedent's "no per-finding suppression" stance.tools.web.fetch.enabled: falsesuppresses the warning. When the tool is disabled,web_fetchcannot make direct requests, so the proxy opt-in diagnostic would be misleading.NO_PROXY=*suppresses the warning (see gate description above): with every target bypassed, the remediation could never change routing. Per-targetNO_PROXYrouting behavior after opt-in remains out of scope — sibling issue web_fetch useTrustedEnvProxy ignores NO_PROXY env variable #93807 owns that surface (fix(web-fetch): respect NO_PROXY when useTrustedEnvProxy is enabled #94541, #93807: fix: respect NO_PROXY in web_fetch useTrustedEnvProxy mode #93840, fix(web-fetch): respect NO_PROXY env var when useTrustedEnvProxy is enabled #93860).User Impact
Operators with
HTTP_PROXY/HTTPS_PROXYset who runopenclaw doctor,openclaw status, oropenclaw security audit --deepsee a Security-section warning that names the env var, points attools.web.fetch.useTrustedEnvProxy, and explains the SSRF / DNS-pinning trade-off of enabling it. Operators without proxy env vars, with the opt-in already enabled, withweb_fetchexplicitly disabled, or withNO_PROXY=*bypassing every target see no warning. There is no behavior change forweb_fetchitself.Evidence
Fresh-base merge update
Merged
openclaw/openclaw@76a236da5fainto the PR branch with a signed merge commit (6dafc2c64aa), then added theNO_PROXY=*gate as signed commit0287d4a4233(current head).mainkept advancing during proof (f180638e878at push time); 0 commits in76a236da..f180638etouch the 7 PR files or the dependency surface (proxy-env.ts,web-fetch.ts), andgit merge-tree --write-tree upstream/main HEADis conflict-free, so the branch was intentionally not re-merged again.Focused regression on the three changed surfaces
Red-first: the four new suppression tests (
NO_PROXY=*on doctor/status/audit plus lowercaseno_proxy=*on doctor) were added before the resolver change and fail against the previous head — the warning is still emitted — then pass with the gate. Coverage now includes:HTTP_PROXY/HTTPS_PROXY, positive lowercasehttp_proxy/https_proxy, mixed caseALL_PROXY/all_proxyonly stays negative (dispatcher does not honor those here)tools.web.fetch.useTrustedEnvProxy: truestays negativetools.web.fetch.enabled: falsestays negative across doctor, status, and security auditNO_PROXY=*and lowercaseno_proxy=*suppress across doctor, status, and security auditNO_PROXY="*,localhost"(a*entry inside a list) still warns, matchingmatchesNoProxy()per-entry skipNO_PROXY=internal.example.comstill warnsno_proxy=shadowingNO_PROXY=*still warns, matching undici precedenceNO_PROXY/no_proxy, so a machine-levelNO_PROXYcannot flip the suiteFull built CLI proof
Built and exercised head
0287d4a4233in a fresh isolated clone with Nodev24.15.0:The built output contains the new gate:
Then ran the built CLI (
node dist/entry.js) with isolatedOPENCLAW_HOME/OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATHandHTTP_PROXY=http://127.0.0.1:7897, across four env/config sets, three surfaces each (doctor--non-interactive,status --json --timeout 1000,security audit --deep --json; all runs exit 0):1. Positive control (no
NO_PROXY, default config): all three surfaces report the diagnostic.status:
secretDiagnosticscontains the opt-in diagnostic; audit: findings includetools.web.fetch.env_proxy_without_use_trusted_env_proxy(warn).2.
NO_PROXY=*(all-bypass): the diagnostic is gone from all three surfaces."useTrustedEnvProxy is not enabled"absent,"web_fetch will use direct connections"absent"secretDiagnostics": []tools.web.fetch.env_proxy_without_use_trusted_env_proxy:[]3.
NO_PROXY=internal.example.com(partial): the diagnostic is present on all three surfaces — partial bypass lists do not suppress.4.
tools.web.fetch.enabled: false(noNO_PROXY): the diagnostic stays absent on all three surfaces, re-proven at this head.Assertion sweep over the captured outputs: 10/10 target-string checks passed (3 positive-control present, 4 all-bypass absent, 3 partial present).
Local gates
corepack pnpm install --frozen-lockfile/corepack pnpm build: exit 0 with Nodev24.15.0node scripts/run-oxlint.mjson the 4 touched files: 0 warnings, 0 errorsoxfmt --checkon the 4 touched files: cleanpnpm tsgo:coreandpnpm tsgo:core:test: exit 0git diff --check upstream/main...HEAD: cleanWhat was not tested
dist/entry.jsCLI.NO_PROXYrouting behavior after the opt-in is enabled (sibling web_fetch useTrustedEnvProxy ignores NO_PROXY env variable #93807 owns that surface). This PR only recognizes the bareNO_PROXY=*all-bypass value in the diagnostic gate.Risks / Mitigations
useTrustedEnvProxy: false. Mitigation: Matches runtime semantics (=== truegate); message is informational and points at the trade-off.src/gateway/but its subject istools.web.fetch.*. Mitigation: Mirrors the [Feature]: doctor/status should warn when OPENCLAW_GATEWAY_TOKEN in ~/.openclaw/.env overrides gateway.auth.token #74271 precedent path for ease of review. Easy to move in a follow-up if the maintainer prefers a different placement.tools.web.fetch.enabled: falseor the exact bareNO_PROXY=*/no_proxy=*value thatmatchesNoProxy()treats as bypass-everything. Positive control and partial-NO_PROXYbuilt-CLI runs plus the per-surface positive tests prove normal proxy users still see the warning.