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
- 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 or scopedregisterBypass()/withBypass()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.
pnpm add @openclaw/proxyline
# or
npm install @openclaw/proxylineRequires Node 20.18.1+ and a host undici dependency compatible with >=7.25.0 <9.
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/"));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");
}import WebSocket from "ws";
const socket = new WebSocket("wss://events.example.com/", {
agent: proxy.createWebSocketAgent(),
});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,
});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.
| Surface | Covered | Notes |
|---|---|---|
http.request / http.get |
yes | global method patch + global agent swap |
https.request / https.get |
yes | global method patch + global agent swap |
fetch / undici global dispatcher |
yes | globalThis.fetch patch + setGlobalDispatcher |
WebSocket clients accepting a Node agent |
yes | proxy.createWebSocketAgent() |
Caller-built http.Agent / https.Agent |
overridden in managed and active ambient mode | TLS options preserved |
| Explicit HTTP CONNECT socket | yes | openProxyConnectTunnel() |
Raw net.connect / tls.connect |
no | out of scope, see Security |
| Native or private transport stacks | no | out of scope, see Security |
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.
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
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.