@openclaw/proxyline
v0.3.2
Published
Process-global proxy routing for Node.js.
Downloads
29,745
Readme
🌐 Proxyline
Process-global proxy routing for Node.js. One install replaces node:http, node:https, the undici/fetch global dispatcher, and provides WebSocket and explicit HTTP CONNECT helpers for the same policy.
Proxyline exists to make proxy behavior explicit, observable, and hard to bypass accidentally — so that "all egress goes through this gateway" is something you encode in code rather than hope for from environment variables.
Proxyline's runtime assurances assume it is installed before application and plugin networking code is loaded. Code that captured networking functions before installation, uses raw sockets, or owns a private/native transport stack is outside the normal Proxyline model.
Website: proxyline.dev
Highlights
- Two modes.
managedforces traffic through a configured proxy and fails closed on bad config.ambientreadsHTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXYfor tooling that needs environment compatibility. - Covers the surfaces that matter.
http.request,http.get,https.request,https.get, both global agents, the undici global dispatcher, and helpers for WebSocket agents and HTTP CONNECT sockets. - Replaces caller agents. In managed mode and active ambient mode, a per-request
http.Agentpassed by a library does not bypass the proxy. TLS options on the caller agent (ca,cert,key,rejectUnauthorized, …) are preserved so destination TLS still validates. - Intentional bypasses only. Managed mode can accept a
bypassPolicycallback, process-wideregisterBypass(), or async-scopedwithBypass()calls for trusted loopback or control-plane traffic that must stay direct; every bypass is visible throughexplain(). - Embeddable runtime controls.
ifActivehandles process singleton reuse/replacement,undicioptions tune dispatcher defaults, andisProxylineDispatcher()identifies Proxyline-owned dispatchers without constructor-name checks. - Scoped proxy CA trust.
proxyTls.ca/proxyTls.caFiletrust a private CA for the proxy endpoint only — noNODE_EXTRA_CA_CERTSand noNODE_TLS_REJECT_UNAUTHORIZED=0. - Observable.
proxy.explain(url)returns a structured decision (proxied/directwith areason), and anonEventcallback receivesruntime.installed,runtime.stopped, and per-decision events. Proxy URLs are credential-redacted. - Restoreable.
proxy.stop()restores the captured Node HTTP(S) methods, global agents, undici dispatcher, and fetch globals. The runtime is a process-wide singleton; by default a second active install throwsRUNTIME_ALREADY_ACTIVE, whileifActivecan reuse or replace intentionally.
Install
pnpm add @openclaw/proxyline
# or
npm install @openclaw/proxylineRequires Node 20.18.1+ and a host undici dependency compatible with >=7.25.0 <9.
Quick start
Managed mode
import { installGlobalProxy } from "@openclaw/proxyline";
const proxy = installGlobalProxy({
mode: "managed",
proxyUrl: "https://proxy.corp.example:8443",
proxyTls: { caFile: "/etc/proxy-ca.pem" },
onEvent: (event) => console.debug("[proxyline]", event),
});
console.log(proxy.explain("https://api.example.com/"));Ambient mode
import { installGlobalProxy } from "@openclaw/proxyline";
const proxy = installGlobalProxy({ mode: "ambient" });
if (!proxy.active) {
console.warn("no HTTP_PROXY/HTTPS_PROXY/ALL_PROXY set — egress will be direct");
}WebSocket
import WebSocket from "ws";
const socket = new WebSocket("wss://events.example.com/", {
agent: proxy.createWebSocketAgent(),
});Explicit HTTP CONNECT
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,
});Conditional Node agent
import { createAmbientNodeProxyAgent } from "@openclaw/proxyline";
const agent = createAmbientNodeProxyAgent({
protocol: "https",
proxyTls: { caFile: "/etc/proxy-ca.pem" },
});The helper returns undefined when ambient proxy env is not configured, so callers can pass an agent only when needed.
It uses Proxyline's built-in HTTP/HTTPS Node agent, and proxyTls applies only to HTTPS proxy endpoints. SOCKS and PAC proxy schemes remain unsupported.
Product coverage
http.request/http.get: covered by global method patching and global agent replacement.https.request/https.get: covered by global method patching and global agent replacement.fetch/ undici global dispatcher: covered by theglobalThis.fetchpatch andsetGlobalDispatcher.- WebSocket clients accepting a Node
agent: covered withproxy.createWebSocketAgent(). - Caller-built
http.Agent/https.Agent: overridden in managed and active ambient mode, with TLS options preserved. - Explicit HTTP CONNECT sockets: covered with
openProxyConnectTunnel(). - Raw
net.connect/tls.connect: out of scope; see Security. - Native or private transport stacks: out of scope; see Security.
Why not just env vars?
Environment-based proxies are best-effort. A missing variable, a stale shell, a NO_PROXY typo, or a library that built its own Dispatcher quietly turns "always through the proxy" into "sometimes direct." Proxyline encodes the policy in code, replaces caller-built agents, and exposes a structured decision so logs can prove every request went the right way.
For tooling that should honor whatever the operator configured, ambient mode keeps the conventional behavior — with the same observability and the same credential redaction.
Documentation
Full docs live in docs/:
- Getting Started
- Modes — managed vs ambient
- Surfaces — per-API behavior
- API Reference
- Environment Variables
- Proxy TLS
- Observability
- Security
- Troubleshooting
- Testing
Limits
Proxyline is a Node-process runtime, not an operating-system sandbox. Code can still bypass it by using raw net, raw tls, custom native networking, or a library that owns a private transport stack. Anything that captured http.request or https.request before Proxyline installed also bypasses it — install before loading third-party integrations when proxy routing is a security policy. See docs/security.md for the full threat model.
