Outbound network proxy

Managed Runtime HTTP keeps URL, credential, redirect and TLS policy while following the user's ordinary proxy for public destinations. When a request goes through that proxy, the proxy resolves the target hostname; the client validates the target first, then pins and checks the proxy socket peer.

Problem

A direct, peer-pinned connection cannot reach services that are only reachable through the user's proxy. Fake-IP proxies such as Clash also answer local DNS queries from the benchmarking range 198.18.0.0/15, so a policy that trusts only the local resolver sees every public hostname as a non-global address and refuses it (NON_GLOBAL_ADDRESS), even though the real resolution happens inside the proxy.

Routing boundaries

The route is chosen before the target policy runs, for every request and every redirect hop, so the policy and the transport always agree on whether a proxy carries the request. A host matched by NO_PROXY, or a process without any route, connects directly with the full local address check.

Proxy-resolved targets

When a public request is routed through the proxy, the policy evaluates it as proxy-resolved:

Fixed audited HTTPS services also accept fake-IP answers on direct connections, because the caller cannot choose their hostname and TLS verifies it. Untrusted public URLs never accept a fake-IP answer on a direct connection.

Resolution and transport

  1. An explicit security.outbound_url.policy_proxy takes precedence and retains its enforcement requirement; ordinary routing is then unused.
  2. OPENPROGRAM_PROXY_URL overrides ordinary routing while preserving NO_PROXY.
  3. HTTPX resolves standard environment variables, with Python's macOS and Windows system settings fallback when applicable. Loopback hosts are always bypassed.

HTTP, HTTPS and SOCKS5 proxies use httpcore proxy pools with a decision-aware backend. HTTPS targets are tunnelled by hostname with CONNECT; plain HTTP targets are forwarded in absolute form. The proxy authority is an owner-selected route; metadata and link-local addresses remain forbidden. Proxy authentication is passed only to the proxy handshake. TLS verifies the original service hostname, the Host header is generated from the validated origin, and redirects cannot forward credentials across origins. Failures never fall back to a direct connection.

The routing map is immutable within a client and part of shared-client cache identity. A new call after routing changes retires stale clients. Explicitly supplied transport-security objects stay deterministic and do not inherit ambient routing.

Accepted and excluded choices

Reuse HTTPX's environment parsing and URLPattern precedence instead of implementing a second NO_PROXY matcher. Keep ordinary routing separate from PolicyProxyConfig: a normal Clash proxy does not assert arbitrary URL policy enforcement, so local checks still run in front of it. Do not introduce a new profile, disable URL protection, modify OAuth credentials or extend ordinary routing to configured custom services. CA bundles remain explicitly controlled by outbound security configuration.

Accepted residual risk: behind a proxy the final resolution is the proxy's, so a public hostname whose real answer is private is caught only when the local resolver returns that real answer. Behind a fake-IP resolver, or with DNS rebinding between the local check and the proxy's lookup, the proxy's own rules decide. Read-only methods, the 80/443 port limit for untrusted URLs and credential stripping still apply. Owners who need a strict guarantee configure an enforcing policy proxy or list hosts in NO_PROXY.

Acceptance

Public sync and async entries use HTTP, HTTPS and SOCKS5 routes; web_fetch behind a fake-IP resolver reaches the target through the proxy by hostname; IP literals, local names and real private answers stay refused when proxied; NO_PROXY hosts keep the direct check; a proxy-resolved decision never opens a direct socket; NO_PROXY and override precedence hold; shared clients are replaced when routes change; unauthorized target and proxy addresses are rejected before network activity; service and proxy credentials stay isolated; explicit policy-proxy behavior and cleanup are retained.

Implementation status

Implemented in openprogram/security/url_policy.py (proxy_resolves, requires_proxy), openprogram/security/safe_http.py (uses_ambient_proxy, route-before-policy) and load_outbound_security_config. The sandbox command proxy (openprogram/sandbox/network_policy.py) keeps its own direct public-address rule and does not yet recognise fake-IP answers. A live model reply also requires valid provider credentials and available service quota.

2. How OpenClaw does it

Source: references/openclaw/src/infra/net/proxy-env.ts, proxy-fetch.ts, src/infra/net/proxy/, and https://docs.openclaw.ai/cli/proxy/.

OpenProgram borrows the useful diagnostic property that standard environment variables have one documented interpretation, while its managed provider path adds a separate policy-proxy boundary. OpenClaw goes further with proxy lifecycle validation and SSRF gating, which this design does not need.