Skip to content

fix(net): respect HTTP proxy env vars in web_fetch SSRF dispatcher#25573

Closed
makew0rld wants to merge 3 commits intoopenclaw:mainfrom
eqtylab:fix/web-fetch-proxy-support-v2
Closed

fix(net): respect HTTP proxy env vars in web_fetch SSRF dispatcher#25573
makew0rld wants to merge 3 commits intoopenclaw:mainfrom
eqtylab:fix/web-fetch-proxy-support-v2

Conversation

@makew0rld
Copy link
Copy Markdown

@makew0rld makew0rld commented Feb 24, 2026

Summary

  • Problem: web_fetch (and all fetchWithSsrFGuard callers) ignore HTTP_PROXY/HTTPS_PROXY env vars because createPinnedDispatcher creates a plain undici Agent that overrides the global dispatcher, bypassing all proxy settings.
  • Why it matters: Users behind corporate proxies, VPNs, or restricted networks cannot use web_fetch at all, even when proxy env vars are correctly configured and work for other HTTP clients (e.g., Anthropic API calls, curl).
  • What changed: createPinnedDispatcher now detects proxy env vars and returns an undici EnvHttpProxyAgent (which reads HTTP_PROXY, HTTPS_PROXY, NO_PROXY automatically) instead of a plain Agent. Added 4 tests covering all env var variants.
  • What did NOT change (scope boundary): SSRF hostname/IP pre-checks in resolvePinnedHostnameWithPolicy still run before the dispatcher is created. The non-proxy code path (plain Agent with DNS pinning) is untouched. web_search bare fetch() calls are not in scope.

This is a resubmission of #24625, which was closed via automated stale-fix triage because #2102 was closed as NOT_PLANNED. The bug is still present on current main — createPinnedDispatcher still creates a plain Agent with no proxy awareness.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

web_fetch (and other tools using fetchWithSsrFGuard) now route through the user's configured HTTP proxy when HTTP_PROXY/HTTPS_PROXY (or lowercase variants) env vars are set. NO_PROXY is also respected via EnvHttpProxyAgent.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? Yes — when proxy env vars are set, web_fetch HTTP requests are routed through the proxy instead of directly. This is user-initiated behavior (requires explicit proxy env var configuration).
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: DNS pinning is skipped when proxying (the proxy handles DNS). SSRF hostname and resolved-IP pre-checks in resolvePinnedHostnameWithPolicy still execute before the dispatcher is created, maintaining protection against requests to private/internal addresses. The proxy path only activates when the user has explicitly configured proxy env vars.

Repro + Verification

Environment

  • OS: Linux (tested on VM behind HTTP proxy)
  • Runtime/container: Node.js 22+
  • Integration/channel: N/A (agent tool)
  • Relevant config: HTTP_PROXY=http://proxy:8080, HTTPS_PROXY=http://proxy:8080

Proxy setup

For systemd-managed OpenClaw (e.g. gateway running as a service), env vars can be set globally via a drop-in override:

sudo systemctl edit openclaw-gateway

Then add:

[Service]
Environment=HTTP_PROXY=http://proxy-host:port
Environment=HTTPS_PROXY=http://proxy-host:port
Environment=NODE_EXTRA_CA_CERTS=/path/to/ca.pem

Then sudo systemctl daemon-reload && sudo systemctl restart openclaw-gateway.

Steps

  1. Set HTTP_PROXY/HTTPS_PROXY env vars pointing to a proxy server (via systemd override, shell, etc.)
  2. Run OpenClaw gateway with these env vars
  3. Use web_fetch to fetch any URL

Expected

Request is routed through the configured proxy.

Actual (before fix)

Request bypasses proxy — the custom undici Agent dispatcher overrides global proxy settings.

Evidence

  • Failing test/log before + passing after
  • 4 new unit tests verify EnvHttpProxyAgent is used when proxy env vars are set, and plain Agent when not
  • Manually verified on a VM behind an HTTP proxy: web_fetch requests now appear in proxy logs

Human Verification (required)

  • Verified scenarios: web_fetch successfully routes through proxy on a VM with HTTPS_PROXY set via systemd override; all existing SSRF tests pass (36 tests across 3 test files)
  • Edge cases checked: lowercase env vars (http_proxy), missing env vars (falls back to plain Agent), mixed env var configurations
  • What you did not verify: NO_PROXY exclusion behavior (delegated to undici's EnvHttpProxyAgent implementation), web_search tool (uses bare fetch(), out of scope)

Compatibility / Migration

  • Backward compatible? Yes — no behavior change when proxy env vars are not set
  • Config/env changes? No — reads existing standard env vars
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Unset HTTP_PROXY/HTTPS_PROXY env vars to fall back to plain Agent (existing behavior)
  • Files/config to restore: src/infra/net/ssrf.ts
  • Known bad symptoms reviewers should watch for: web_fetch failing with connection errors when proxy env vars are set but proxy is unreachable

Risks and Mitigations

  • Risk: DNS pinning is bypassed when using a proxy, which widens the TOCTOU window for DNS rebinding.
    • Mitigation: SSRF hostname and resolved-IP pre-checks still execute before the dispatcher is created. The proxy itself performs DNS resolution, and the user has explicitly opted into proxy routing.

AI-assisted: This PR was developed with Claude Code (Opus). Fully tested on a real proxy setup. I understand what the code does.

Greptile Summary

Adds proxy environment variable support to web_fetch and all fetchWithSsrfGuard callers by detecting HTTP_PROXY/HTTPS_PROXY env vars in createPinnedDispatcher and returning an EnvHttpProxyAgent instead of a plain undici Agent when proxy configuration is detected.

  • Modified createPinnedDispatcher (src/infra/net/ssrf.ts:341-358) to check proxy env vars via new hasProxyEnv helper and conditionally return EnvHttpProxyAgent
  • SSRF hostname/IP pre-checks in resolvePinnedHostnameWithPolicy still execute before dispatcher creation, maintaining security boundary
  • DNS pinning is bypassed when proxying (proxy performs DNS resolution), which slightly widens TOCTOU window but is user-opted behavior
  • Added 4 test cases covering all proxy env var combinations (uppercase/lowercase variants)
  • Tests verify correct dispatcher type based on env var presence

Confidence Score: 4/5

  • Safe to merge with minor risk - SSRF pre-checks remain intact and proxy behavior only activates with explicit env var configuration
  • The implementation is straightforward and maintains existing SSRF protections. Security pre-checks in resolvePinnedHostnameWithPolicy execute before the proxy dispatcher is created. The main trade-off is DNS pinning bypass when proxying, but this is an acceptable security posture for user-configured proxy scenarios. Tests adequately cover the proxy detection logic.
  • No files require special attention

Last reviewed commit: 1db58db

(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!

When HTTP_PROXY/HTTPS_PROXY env vars are set, createPinnedDispatcher now
returns an undici EnvHttpProxyAgent instead of a plain Agent. This allows
web_fetch (and all fetchWithSsrFGuard callers) to route through the
configured proxy. DNS pinning is skipped when proxying since the proxy
handles resolution; SSRF hostname/IP pre-checks still run beforehand.

Related openclaw#2102
Related openclaw#8534
Supersedes openclaw#24625

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Use delete for undefined env vars instead of assigning undefined
(which would set them to the string "undefined").

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 3, 2026
@makew0rld
Copy link
Copy Markdown
Author

This is not outdated, I just need a review and merge.

@openclaw-barnacle openclaw-barnacle bot removed the stale Marked as stale due to inactivity label Mar 4, 2026
Pass proxyTunnel: false to EnvHttpProxyAgent so plain HTTP targets
use proper absolute-URI style proxying (GET http://host/path) instead
of CONNECT tunneling. Some proxies reject CONNECT for non-TLS traffic.
HTTPS targets still correctly use CONNECT regardless of this flag.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 30, 2026
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Mar 31, 2026

Closing as duplicate; this was superseded by #50650.

@obviyus obviyus closed this Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants