Surfaces
Proxyline installs at the layer above each network API. It does not own sockets, except via the explicit openProxyConnectTunnel helper.
#node:http and node:https
Both modules have their request entry points patched and their globalAgent replaced.
Patched methods:
http.request,http.gethttps.request,https.get
Per request, Proxyline:
- Copies the call's options object so caller state is not mutated.
- Reads TLS-relevant options off any caller-supplied
agentand lifts them onto the request options (see TLS identity preservation). - Sets
servernameto the destination hostname when not already set and the hostname is not an IP literal. - Builds a fresh
proxy-agentProxyAgentfor the request and assigns it asoptions.agent. - Deletes any
createConnectionoverride so callers cannot punch through to the kernel directly. - Destroys the per-request proxy agent when the request closes.
This means caller agents are not just ignored — the per-request agent is replaced before the original method runs, so even libraries that read req.agent after construction see the proxy agent.
#TLS identity preservation
When the caller supplied an https.Agent with TLS options, the following keys are lifted into the request so the destination TLS handshake still validates correctly:
ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, honorCipherOrder, key, maxVersion, minVersion, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, sessionIdContext.
The destination servername is also inferred from the URL or hostname/host options when the caller did not set one. IP literals are not used as SNI values.
#Absolute-form requests
Some HTTP clients build absolute-form requests themselves (e.g. path: "https://api.example.com/graphql"). Proxyline leaves the path intact and forwards it through the proxy unchanged, so libraries that already implement their own proxy handling continue to function.
#undici and fetch
installGlobalProxy calls undici.setGlobalDispatcher with:
undici.ProxyAgentin managed mode, pointed atproxyUrland trustingproxyTlswhen supplied.undici.EnvHttpProxyAgentin ambient mode, configured from the currentHTTP_PROXY/HTTPS_PROXY/NO_PROXYsnapshot, with the sameproxyTls.
The original dispatcher is captured and restored on stop().
import { fetch } from "undici";
await fetch("https://api.example.com/health"); // routed through Proxyline
If your code creates its own undici Agent or Dispatcher and passes it explicitly to fetch, that explicit instance wins. Use proxy.createUndiciDispatcher() to get a dispatcher pre-wired to the same policy.
#WebSocket
WebSocket clients that accept a Node agent option (e.g. ws) can route through the proxy via:
import WebSocket from "ws";
const socket = new WebSocket("wss://events.example.com/", {
agent: proxy.createWebSocketAgent(),
});
When ambient mode is inactive, createWebSocketAgent() returns a plain http.Agent, so calling code does not need a conditional path.
Clients that do not expose an agent option but still route their handshake through http.request are covered automatically by the global patch.
#HTTP CONNECT tunnel
Some libraries — notably HTTP/2 clients — need ownership of the underlying socket. openProxyConnectTunnel performs an HTTP CONNECT against the proxy and resolves with a connected net.Socket (or tls.TLSSocket for HTTPS proxies).
import { openProxyConnectTunnel } from "@openclaw/proxyline";
const socket = await openProxyConnectTunnel({
proxyUrl: "https://proxy.corp.example:8443",
proxyTls: { caFile: "/etc/proxy-ca.pem" },
targetHost: "api.example.com",
targetPort: 443,
timeoutMs: 2_000,
});
Properties:
- HTTP and HTTPS proxy endpoints supported. Other schemes throw
UNSUPPORTED_PROXY_PROTOCOL. - HTTPS proxies use ALPN
http/1.1. SNI is the proxy hostname unless that is an IP literal. - Userinfo in the
proxyUrlbecomes aProxy-Authorization: Basic ...header. - A bounded
16 KiBheader buffer protects against malicious or runaway proxy responses. timeoutMsis enforced and emits aCONNECT_FAILEDerror on expiry.- Bytes the proxy sends after the response headers are re-injected with
socket.unshift()so the caller sees the full target stream. - Non-2xx status lines, header overrun, premature close, and socket errors are all surfaced as
ProxylineErrorwith codeCONNECT_FAILED.
The helper is standalone — it does not require installGlobalProxy to have been called.
#Caller-built agents
A http.Agent or https.Agent you construct yourself is replaced in managed mode and replaced when active in ambient mode. The replacement happens inside the patched http.request / https.request call: by the time the underlying request starts, options.agent already points at a proxy agent.
This is intentional: a common bypass is new https.Agent({ keepAlive: true }) passed per-request. Without replacement the proxy is silently skipped.
To get a proxy-aware agent without going through the patched globals, use:
proxy.createNodeAgent()— anhttp.Agentthat proxies HTTP and HTTPS requests.proxy.createUndiciDispatcher()— an undiciDispatchermirroring the current mode.proxy.createWebSocketAgent()— anhttp.Agentsuitable for WebSocket upgrades.
When the runtime is inactive (ambient mode with no env proxy set), these helpers return plain agents/dispatchers that go direct.